计算生成器/迭代器中项目数的最短方法是什么?


73

如果我希望迭代中的项目数量而不关心元素本身,那么实现该目标的Python方法是什么?现在,我将定义

def ilen(it):
    return sum(itertools.imap(lambda _: 1, it))    # or just map in Python 3

但我知道自己lambda几乎被认为是有害的,而且lambda _: 1肯定不是很漂亮。

(这种情况的用例是计算文本文件中与regex相匹配的行数,即grep -c。)


5
请不要将其_用作变量名,因为(1)它容易使人们感到困惑,使他们认为这是一种特殊的语法,(2)_在交互式解释器中发生冲突,以及(3)与常见的gettext别名发生冲突。
Sven Marnach 2011年

5
@Sven:我_一直在使用未使用的变量(Prolog和Haskell编程的习惯)。(1)首先是要提出这个问题的原因。我没有考虑(2)和(3),谢谢指出!
Fred Foo


python 3.x,如果存在重复的项目,并且您还想检查每个项目的计数,请使用Counter(generator/iterator),例如c = Counter(iter('goodbadugly')),然后对总数进行计数:sum(c.values())
Kuo 2012年

Answers:


158

itertools.imap()在Python 2或map()Python 3中的调用可以用等效的生成器表达式替换:

sum(1 for dummy in it)

这也使用了惰性生成器,因此避免了实现内存中所有迭代器元素的完整列表。


3
您可以使用len(list(it))-或如果元素是唯一的,则len(set(it))可以保存字符。
F1Rumors

27
@ F1Rumorslen(list(it))在大多数情况下使用很好。但是,当您有一个懒惰的迭代器产生大量元素时,就不想同时将它们全部存储在内存中只是为了对它们进行计数,这可以避免使用此答案中的代码。
Sven Marnach '16

同意:作为答案,它的前提是“最短代码”比“最低内存”更重要。
F1Rumors

2
如该线程中所建议的那样,sum(1 for _ in generator)避免填充内存。
西尔万

37

该方法比sum(1 for i in it)可迭代的对象可能较长时有意义地快(而在可迭代的对象较短时则有意义地慢),同时保持固定的内存开销行为(不同于len(list(it))),以避免较大输入的交换崩溃和重新分配开销:

# On Python 2 only, get zip that lazily generates results instead of returning list
from future_builtins import zip

from collections import deque
from itertools import count

def ilen(it):
    # Make a stateful counting iterator
    cnt = count()
    # zip it with the input iterator, then drain until input exhausted at C level
    deque(zip(it, cnt), 0) # cnt must be second zip arg to avoid advancing too far
    # Since count 0 based, the next value is the count
    return next(cnt)

len(list(it))它执行上CPython的C代码环路(dequecountzip在C中的所有实现的); 避免每个循环执行字节码通常是CPython性能的关键。

很难拿出公平的测试用例来比较性能(用list作弊手段__length_hint__不可能对任意输入可迭代对象可用,作弊的itertools功能__length_hint__通常没有特殊的操作模式,当每次循环返回值时,它们的工作速度更快,这是令人惊讶的。被释放/释放请求先下一值,这dequemaxlen=0将做)。我使用的测试用例是使用Python 3.3创建一个生成器函数,该函数将接受输入并返回缺少特殊itertools返回容器优化或的C级生成器:__length_hint__yield from

def no_opt_iter(it):
    yield from it

然后使用ipython %timeit魔术(将不同的常数替换为100):

>>> %%timeit -r5 fakeinput = (0,) * 100
... ilen(no_opt_iter(fakeinput))

当输入的大小不足以len(list(it))导致内存问题时,在运行Python 3.5 x64的Linux机器上def ilen(it): return len(list(it)),无论输入长度如何,我的解决方案都比花费大约50%的时间。

为最小的输入,设置成本到呼叫deque/ zip/ count/next装置需要无限长这种方式比def ilen(it): sum(1 for x in it)(约200纳秒更我的机器上用于长度为0的输入,这是通过简单增加了33%sum的方法),但对于输入越长,每个附加元素的运行时间减少一半左右;对于长度为5的输入,成本是等效的,并且在长度50-100之间的某个位置,与实际工作相比,初始开销并不明显;该sum方法大约需要两倍的时间。

基本上,如果内存使用问题或输入没有限制的大小,并且您更关注速度而不是简洁性,请使用此解决方案。如果输入是有界且很小的,len(list(it))则可能是最好的;如果输入是无界的,则简单/简洁起着重要作用,则可以使用sum(1 for x in it)


这正是中的实现more_itertools.ilen
rsalmei

3
@rsalmei:好像他们八个月前通过了我的实现。从技术上讲,它稍慢一些(因为它们是通过maxlen关键字传递的,而不是位置传递的),但这是固定的开销,在big-O运行时没有意义。无论哪种方式,他们都抄袭了我(我是在3.5年前发布的),而不是相反。:-)
ShadowRanger

不错的解决方案。作为一个观察-如果它是“令人惊讶的很难拿出公平的测试用例比较性能,”那么或许是值得的没有通用的解决方案,这将是最好的时间不同的实现(这一个,sum(1 ..)len(list()),等。 )到一个人的特殊情况。
user650654

9

一个简短的方法是:

def ilen(it):
    return len(list(it))

请注意,如果生成大量元素(例如成千上万个或更多),那么将它们放在列表中可能会成为性能问题。但是,这只是这种想法的简单表达,在大多数情况下,性能并不重要。


1
我想到了这一点,但是性能确实很重要,因为我经常处理大型文本文件。
弗雷德·富

8
只要您不用完内存,此解决方案实际上就性能而言是相当不错的,因为这将在纯C代码中进行循环-无论如何都必须生成所有对象。即使对于大型迭代器sum(1 for i in it),只要所有内容都适合内存,它的速度也会比它快。
Sven Marnach 2011年

它实际上是疯狂的,len(it)不起作用。sum(it)max(it)min(it)等如预期的工作,只是len(it)没有。
Kai Petzke '18

2
@KaiPetzke:当it是迭代器时,不能保证它知道自己的长度而不会耗尽它。最明显的例子是文件对象。它们的长度取决于文件中的行数,但是行的长度是可变的,要知道行数是多少,唯一的方法是读取整个文件并计算换行数。len()旨在廉价O(1)运行;您是否希望它在询问多GB文件的长度时以静默方式读取它们?summax并且min是必须读取其数据汇总功能,len是不是。
ShadowRanger

@ShadowRanger:一个选项可能是添加O(n)集合count(it)
Kai Petzke '18

7

more_itertools是实现ilen工具的第三方库。 pip install more_itertools

import more_itertools as mit


mit.ilen(x for x in range(10))
# 10

1
值得注意的是,这基本上实现了另一个答案。(不要误会我的意思。我所有人都不必编写自己的代码,所以我喜欢这个答案,特别是因为more_itertools还有很多其他事情。我想记下来。)
jpmc26

1

我喜欢基数软件包,它非常轻巧,并根据可迭代性尝试使用最快的实现。

用法:

>>> import cardinality
>>> cardinality.count([1, 2, 3])
3
>>> cardinality.count(i for i in range(500))
500
>>> def gen():
...     yield 'hello'
...     yield 'world'
>>> cardinality.count(gen())
2

1

这些可能是我的选择之一:

print(len([*gen]))
print(len(list(gen)))

1
第一种选择似乎没有什么意义,因为这只会增加在将整个生成器转换为之前扩展整个生成器的开销list。除非您可以解释为什么第一个选择有任何优点,否则此答案的含义并不比其他答案有价值。
jpmc26

@ jpmc26,OP要求计算生成器中元素数量的最短方法。len([*gen])很短。例如,这在Code Golf中将很有价值。但是,我同意您的观点,在大多数使用情况下,这种解决方案都不理想。
ruancomelli

实际上,标题中写着“最短的方法”,但问题的内容却大不相同。len([*gen])对我感到不可思议。
ruancomelli
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.