要计算列表中的元素,可以使用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
此时我退了一步,重新跑了几次。三个Counter版本(未过滤,列表理解,生成器表达式)的速度几乎相等。 defaultdict
版本一直慢得多。
如何在同时过滤元素的同时有效地计算python列表中的元素?
如果这是关于大型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))
相比之下,我自己的代码时间略高于你的代码。