python计数与过滤器迭代

时间:2019-02-21 09:04:50

标签: python iterator iterable python-collections

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

1 个答案:

答案 0 :(得分:2)

如果这是有关大型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))

为了进行比较,我自己的代码时间比您的时间高出很多。