0

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

4

1 回答 1

3

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

​相比之下,我自己的代码时间比你的略高。

于 2019-02-21T09:31:53.970 回答