Python подсчет элементов в итерируемых с фильтром
Для подсчета элементов в списке вы можете использовать collections.Counter
, но что, если нужно подсчитать только некоторые элементы?
Я настроил этот пример (обратите внимание: numpy только для удобства. В общем, список будет содержать произвольные объекты Python):
num_samples = 10000000
num_unique = 1000
numbers = np.random.randint(0, num_unique, num_samples)
Я хотел бы посчитать, как часто число встречается в этом списке, но меня интересуют только цифры <= 10.
Это базовый уровень, чтобы победить. Счетчик просто считает все, что должно привести к накладным расходам.
%%time
counter = Counter(numbers)
CPU times: user 1.38 s, sys: 7.49 ms, total: 1.39 s
Wall time: 1.39 s
Фильтрация итерируемого при подсчете кажется невозможной. Но следующий код очень плохой стиль, он проходит по списку дважды, вместо использования одного цикла:
%%time
numbers = [number for number in numbers if number<=10]
counter = Counter(numbers)
CPU times: user 1.3 s, sys: 22.1 ms, total: 1.32 s
Wall time: 1.33 s
Это ускорение в основном незначительно. Давайте попробуем один цикл:
%%time
counter = defaultdict(int)
for number in numbers:
if number > 10:
continue
counter[number]+=1
CPU times: user 1.99 s, sys: 11.5 ms, total: 2 s
Wall time: 2.01 s
Ну, моя единственная петля намного хуже. Я предполагаю, что Counter получает прибыль от реализации на основе C?
Следующее, что я попробовал, было переключение моего выражения списка для выражения генератора. В принципе это должно означать, что генератор проходит только один раз, пока он потребляется счетчиком. Числа разочаровывают, хотя, это в основном так же быстро, как ванильный счетчик:
%%time
iterator = (number for number in numbers if number <= 10)
counter = Counter(iterator)
CPU times: user 1.38 s, sys: 8.51 ms, total: 1.39 s
Wall time: 1.39 s
В этот момент я сделал шаг назад и перезапустил цифры несколько раз. Три версии счетчика (нефильтрованные, составление списка, выражение генератора) почти одинаковы по скорости. defaultdict
Версия постоянно намного медленнее.
Как я могу эффективно считать элементы в списке Python, одновременно фильтруя элементы?
1 ответ
Если речь идет о больших массивах numpy, лучше воспользоваться векторизованными операциями numpy.
%%time
np.unique(numbers[numbers <= 10], return_counts=True)
Выход:
Wall time: 31.2 ms
(array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]),
array([10055, 10090, 9941, 10002, 9994, 9989, 10070, 9859, 10038,
10028, 9965], dtype=int64))
Для сравнения, мои собственные временные характеристики вашего кода немного превышали ваши.