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

Fre*_*Foo 60 python iterator iterable generator

如果我想要迭代中的项目数量而不关心元素本身,那么获得该元素的pythonic方法是什么?现在,我会定义

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

但我明白lambda接近被认为是有害的,lambda _: 1当然不是很漂亮.

(这个用例是计算匹配正则表达式的文本文件中的行数,即grep -c.)

Sve*_*ach 137

通常的方式是

sum(1 for i in it)
Run Code Online (Sandbox Code Playgroud)

  • @ F1Rumors在大多数情况下使用`len(list(it))`就可以了.但是,当你有一个懒惰的迭代器产生大量的元素时,你不希望同时将它们全部存储在内存中来计算它们,这可以使用这个答案中的代码来避免. (18认同)
  • 您可以使用len(list(it))-或如果元素是唯一的,则可以使用len(set(it))保存字符。 (3认同)
  • 正如此[线程](/sf/answers/522269051/)中所建议的,`sum(1 for _ in Generator)`避免填充内存。 (3认同)

Sha*_*ger 30

有效的方法比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)
Run Code Online (Sandbox Code Playgroud)

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

令人惊讶地难以提出用于比较性能的公平测试用例(list使用__length_hint__哪些不可用于任意输入迭代的作弊itertools,不提供的功能__length_hint__通常具有在每个循环返回的值时更快地工作的特殊操作模式在请求下一个值之前释放释放deque,maxlen=0将执行此操作).我使用的测试用例是创建一个生成器函数,该函数将接受输入并返回缺少特殊itertools返回容器优化的C级生成器,或__length_hint__使用Python 3.3 yield from:

def no_opt_iter(it):
    yield from it
Run Code Online (Sandbox Code Playgroud)

然后使用ipython %timeit魔法(用不同的常数代替100):

>>> %%timeit -r5 fakeinput = (0,) * 100
... ilen(no_opt_iter(fakeinput))
Run Code Online (Sandbox Code Playgroud)

当输入不够大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)(对于长度为0的输入,我的机器上大约200 ns,比简单的sum方法增加了33%),但对于输入时间越长,每个附加元素的运行时间约为一半; 对于长度为5的输入,成本是等价的,并且在50-100范围内的某处,与实际工作相比,初始开销是不明显的; 这种sum方法大约需要两倍的时间.

基本上,如果内存使用很重要或输入没有有限的大小而你关心速度而不是简洁,请使用此解决方案.如果输入是有界的和小的,len(list(it))可能是最好的,如果它们是无界限的,但简单/简洁是重要的,你可以使用sum(1 for x in it).

  • @rsalmei:看起来他们[八个月前采用了我的实现](https://github.com/erikrose/more-itertools/commit/5161c3455375492ce9dfb4ad32a2e5ee1506f966)。从技术上讲,它稍微慢一些(因为他们通过关键字传递“maxlen”,而不是位置),但这是固定的开销,在大O运行时没有意义。不管怎样,他们抄袭了我(我在 3.5 年前发布了这篇文章),而不是相反。:-) (4认同)
  • 这正是“more_itertools.ilen”中的实现。 (2认同)

Gre*_*ill 6

一个简短的方法是:

def ilen(it):
    return len(list(it))
Run Code Online (Sandbox Code Playgroud)

请注意,如果要生成大量元素(例如,数万或更多),则将它们放在列表中可能会成为性能问题.然而,这是一个简单的表达方式,在大多数情况下性能不重要.

  • 只要你没有内存耗尽,这个解决方案实际上在性能上非常好,因为这将在纯C代码中完成循环 - 无论如何都必须生成所有对象.即使是大迭代器,只要一切都适合内存,这比`sum(1 for i in)更快. (7认同)
  • @KaiPetzke:当`it` 是一个迭代器时,不能保证它知道自己的长度而不用完它。最明显的例子是文件对象;它们的长度基于文件中的行数,但行的长度是可变的,知道它们有多少行的唯一方法是读取整个文件并计算换行符。`len()` 旨在成为一个廉价的 `O(1)` 操作;当您询问它们的长度时,您是否希望它静默读取多 GB 文件?`sum`、`max` 和 `min` 是必须读取其数据的聚合函数,而 `len` 不是。 (3认同)

pyl*_*ang 5

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

import more_itertools as mit


mit.ilen(x for x in range(10))
# 10
Run Code Online (Sandbox Code Playgroud)


Nik*_*CSB 5

len(list(it))
Run Code Online (Sandbox Code Playgroud)

不过,如果它是无限生成器,它可能会挂起。

  • 无论如何,没有什么可以计算无限生成器中的项目。 (3认同)