要计算列表中的元素,您可以使用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 列表中的元素,同时过滤元素?