Python:计算列表中满足if条件的元素数量

6

给定一个整数列表,最符合Python编程风格/最佳方法是如何计算在某个范围内有多少个元素?

经过研究,我找到了两种方法:

>>> x = [10, 60, 20, 66, 79, 5]
>>> len([i for i in x if 60 < i < 70])
1

或者:

>>> x = [10, 60, 20, 66, 79, 5]
>>> sum(1 for i in x if 60 < i < 70)
1

哪种方法在处理更大的列表时使用更少的时间/内存?为什么?或许还有其他更好的方法...

你真的需要这个列表吗?如果不需要,第二个版本避免了创建它。 - jonrsharpe
“Most pythonic”并不意味着时间/内存消耗更少,但是您在问题中询问了两者。您想知道哪个是最pythonic或最有效的? - Vincent Savard
你也可以使用 if i in range(61, 70) - pp_
@pp_ 是的,那也可行,但我认为它使用了更多的操作,因为你需要检查x的每个元素是否在列表/迭代器 [61, 62, 63, 64, 65, 66, 67, 68, 69, 70] 中。 - K. Menyah
3个回答

6
在你提供的特定情况下
[i for i in x if 60 < i < 70]

实际上会生成一个全新的列表,然后获取它的 len。相反,
(1 for i in x if 60 < i < 70)

这是一个生成器表达式,你可以对其进行sum操作。

对于足够大的相关项,第二个版本将更加高效(特别是在内存方面)。


时间

x = [65] * 9999999

%%time

len([i for i in x if 60 < i < 70])

CPU times: user 724 ms, sys: 44 ms, total: 768 ms
Wall time: 768 ms
Out[7]:
9999999

%%time

sum(1 for i in x if 60 < i < 70)
CPU times: user 592 ms, sys: 0 ns, total: 592 ms
Wall time: 593 ms

我对你所说的生成器速度优势表示怀疑。我的计时无法复现生成器更快的情况。在列表版本中,您不必执行加法,只需获取长度即可。 - timgeb
@timgeb 在列表版本中,可能会有垃圾回收起到作用。 - Frerich Raabe

5
生成器表达式更加节省 内存,因为您不必创建额外的列表。
对于相对较小的列表,创建一个列表并获取其长度(后者是非常快速的 O(1) 操作)似乎比创建生成器并进行 n 次添加要 更快
In [13]: x = [1]
In [14]: timeit len([i for i in x if 60 < i < 70])
10000000 loops, best of 3: 141 ns per loop
In [15]: timeit sum(1 for i in x if 60 < i < 70)
1000000 loops, best of 3: 355 ns per loop
In [16]: x = range(10)
In [17]: timeit len([i for i in x if 60 < i < 70])
1000000 loops, best of 3: 564 ns per loop
In [18]: timeit sum(1 for i in x if 60 < i < 70)
1000000 loops, best of 3: 781 ns per loop
In [19]: x = range(50)
In [20]: timeit len([i for i in x if 60 < i < 70])
100000 loops, best of 3: 2.4 µs per loop
In [21]: timeit sum(1 for i in x if 60 < i < 70)
100000 loops, best of 3: 2.62 µs per loop
In [22]: x = range(1000)
In [23]: timeit len([i for i in x if 60 < i < 70])
10000 loops, best of 3: 50.9 µs per loop
In [24]: timeit sum(1 for i in x if 60 < i < 70)
10000 loops, best of 3: 51.7 µs per loop

我尝试了各种列表,比如[65]*n,但趋势并没有改变。例如:
In [1]: x = [65]*1000
In [2]: timeit len([i for i in x if 60 < i < 70])
10000 loops, best of 3: 67.3 µs per loop
In [3]: timeit sum(1 for i in x if 60 < i < 70)
10000 loops, best of 3: 82.3 µs per loop

3
您可以使用 timeit 模块轻松测试这一点。对于您的特定示例,第一个基于 len 的解决方案似乎更快:
$ python --version
Python 2.7.10
$ python -m timeit -s "x = [10,60,20,66,79,5]" "len([i for i in x if 60 < i < 70])"
1000000 loops, best of 3: 0.514 usec per loop
$ python -m timeit -s "x = [10,60,20,66,79,5]" "sum(i for i in x if 60 < i < 70)"
1000000 loops, best of 3: 0.693 usec per loop

即使对于更大的列表-但大多数元素不符合您的谓词-len版本似乎也不慢:

$ python -m timeit -s "x = [66] + [8] * 10000" "len([i for i in x if 60 < i < 70])"
1000 loops, best of 3: 504 usec per loop
$ python -m timeit -s "x = [66] + [8] * 10000" "sum(1 for i in x if 60 < i < 70)"
1000 loops, best of 3: 501 usec per loop

实际上,即使给定列表中的大多数元素匹配(因此构建了一个大的结果列表来传递给len),len版本也胜出:

$ python -m timeit -s "x = [66] + [65] * 10000" "len([i for i in x if 60 < i < 70])"
1000 loops, best of 3: 762 usec per loop
$ python -m timeit -s "x = [66] + [65] * 10000" "sum(1 for i in x if 60 < i < 70)"
1000 loops, best of 3: 935 usec per loop

然而,如果可能的话,似乎更快的方法是一开始就不要有列表,而是使用例如collections.Counter。例如,对于100000个元素,我得到:

$ python -m timeit -s "import collections; x = [66] + [65] * 100000" "len([i for i in x if 60 < i < 70])"
100 loops, best of 3: 8.11 msec per loop
$ python -m timeit -s "import collections; x = [66] + [65] * 100000; d = collections.Counter(x)" "sum(v for k,v in d.items() if 60 < k < 70)"
1000000 loops, best of 3: 0.761 usec per loop

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接