Для этого используется поддержка арифметических c операторов в классе Counter
- и set
, и Counter
поддерживают несколько полезных операций:
>>> li = [1, 4, 6, 2, 2, 1, 5, 3, 2]
>>> s = set(li)
>>>
>>> len(li) - len(s) + len(Counter(li) - Counter(s))
5
>>>
len(li) - len(set(li))
дает число дубликаты, или количество элементов списка, оставшихся после того, как мы извлечем set
элементов.
Чтобы получить список элементов набора, связанных с элементом в оставшемся списке:
>>> list((Counter(li) - Counter(set(li))))
[1, 2]
И чтобы получить список дубликатов, оставшихся в списке после удаления всех элементов set
:
>>> list((Counter(li) - Counter(set(li))).elements())
[1, 2, 2]
Если бы была операция вычитания для списков, это то, что мы получили бы после вычитания set
из списка.
Предлагаемая оптимизация
Если возможно, приложение, использующее этот список из 70-80К элементов, должно постепенно наращивать Счетчик с самого начала, как он заполняет список. При необходимости он может иметь свой список, счетчик или другие необходимые структуры, поэтому метрики или другие типы обработки могут быть сокращены на последующих этапах.
Контрольные показатели
В произвольном порядке, вот как долго каждый алгоритм обрабатывал список случайных чисел из 80K.
>>> li = [random.randint(0, 100) for _ in range(80 * 1000)]
>>> n_iter = 1000
>>>
>>> timeit.timeit("s = set(li); "
... "len(li) - len(s) + len(Counter(li) - Counter(s))",
... globals=globals(), number=n_iter)
7.048838693
>>>
>>> timeit.timeit("sum(v for k, v in Counter(li).items() if v > 1)",
... globals=globals(), number=n_iter)
5.787936814
>>>
>>> timeit.timeit(original_posters_script, globals=globals(), number=n_iter)
# Takes too much time to sit through. It's very slow. O(N^2)
>>>
Неудивительно, что самым быстрым алгоритмом является другое решение Counter в выбранном ответе.