在Python中获取迭代器中的元素数量

117 python iterator

有没有一种有效的方法来了解Python中迭代器中有多少元素,一般来说,没有遍历每个元素并进行计数?

Joh*_*ard 199

这段代码应该有效:

>>> iter = (i for i in range(50))
>>> sum(1 for _ in iter)
50
Run Code Online (Sandbox Code Playgroud)

虽然它会遍历每个项目并对它们进行计数,但这是最快的方法.

它也适用于迭代器没有项目时:

>>> sum(1 for _ in range(0))
0
Run Code Online (Sandbox Code Playgroud)

  • 这是一种节省可迭代元素的节省空间的方法 (34认同)
  • @AloisMahdal否.在Python中传统上使用名称`_`作为虚拟变量,其值不关心. (15认同)
  • 在我看来,这确实是OP不想做的事情:遍历迭代器并计算. (9认同)
  • 虽然这不是OP想要的,但鉴于他的问题没有答案,这个答案避免了列表的实例化,并且它通过常量比上面列出的reduce方法更快. (9认同)
  • 无法帮助:是`_`引用Perl的`$ _`?:) (5认同)

Tom*_*cki 92

不,这是不可能的.

例:

import random

def gen(n):
    for i in xrange(n):
        if random.randint(0, 1) == 0:
            yield i

iterator = gen(10)
Run Code Online (Sandbox Code Playgroud)

长度iterator是未知的,直到你迭代它.

  • 或者,`def gen():yield random.randint(0,1)`是无限的,所以你永远无法通过迭代来找到一个长度. (13认同)
  • 因此,为了验证显而易见的事实:获取迭代器“大小”的最佳方法就是简单地计算迭代的次数,对吧?在这种情况下,它是“numIters = 0”;while 迭代器:numIters +=1`? (2认同)
  • @tgray:`while True:`在代码中丢失了。 (2认同)
  • @tgray 这不是无限的,它只有一个元素。 (2认同)

Dae*_*yth 62

不,任何方法都需要您解决每个结果.你可以做

iter_length = len(list(iterable))
Run Code Online (Sandbox Code Playgroud)

但是在无限迭代器上运行它当然永远不会返回.它也将使用迭代器,如果你想使用它,它将需要重置.

告诉我们你想要解决的真正问题可能会帮助我们找到更好的方法来实现你的实际目标.

编辑:使用list()将立即将整个可迭代内容读入内存,这可能是不合需要的.另一种方法是做

sum(1 for _ in iterable)
Run Code Online (Sandbox Code Playgroud)

作为另一个人张贴.这样可以避免将其保留在内存中.

  • @Wilduck:它没有消失,只是转移到`functools.reduce` (7认同)
  • 我不是pysam用户,但它可能正在阅读文件"懒惰".这是有道理的,因为你不想在内存中有大文件.所以,如果你必须不知道.迭代之前的记录,唯一的方法是创建两个迭代器,并使用第一个来计算元素,第二个用于读取文件.BTW.不要使用`len(list(iterable))`它会将所有数据加载到内存中.你可以使用:`reduce(lambda x,_:x + 1,iterable,0)`.编辑:Zonda333代码总和也不错. (6认同)

zuo*_*zuo 29

你不能(除了特定迭代器的类型实现了一些使它成为可能的特定方法).

通常,您只能通过使用迭代器来计算迭代器项.可能是最有效的方法之一:

import itertools
from collections import deque

def count_iter_items(iterable):
    """
    Consume an iterable not reading it into memory; return the number of items.
    """
    counter = itertools.count()
    deque(itertools.izip(iterable, counter), maxlen=0)  # (consume at C speed)
    return next(counter)
Run Code Online (Sandbox Code Playgroud)

(对于Python 3.x的替换itertools.izipzip).

  • +1:在与`sum(迭代器中的_为1)的时间比较中,这几乎快了两倍. (2认同)

bad*_*adp 17

均田.您可以检查该__length_hint__方法,但要注意(至少在Python 3.4中,正如gsnedders帮助指出的那样)它是一个未记录的实现细节(在线程中的消息之后),这可能很好地消失或召唤鼻子恶魔.

否则,没有.迭代器只是一个只暴露next()方法的对象.您可以根据需要多次调用它们,它们可能会或可能不会最终提升StopIteration.幸运的是,这种行为大部分时间对编码人员来说是透明的.:)

  • 从[PEP 424](http://legacy.python.org/dev/peps/pep-0424/)和Python 3.4开始不再是这种情况.现在记录了`__length_hint__`,但它是*提示*并不保证准确性. (5认同)

Erw*_*yer 11

我喜欢它的基数包,它非常轻量级,并尝试使用可用的最快实现,具体取决于iterable.

用法:

>>> 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
Run Code Online (Sandbox Code Playgroud)

实际count()实施如下:

def count(iterable):
    if hasattr(iterable, '__len__'):
        return len(iterable)

    d = collections.deque(enumerate(iterable, 1), maxlen=1)
    return d[0][0] if d else 0
Run Code Online (Sandbox Code Playgroud)


Jes*_*mos 9

迭代器只是一个对象,它有一个指向下一个要被某种缓冲区或流读取的对象的指针,它就像一个LinkedList,在你迭代它们之前你不知道你有多少东西.迭代器意味着效率很高,因为他们只是通过引用而不是使用索引来告诉你接下来是什么(但是你看到你失去了查看接下来有多少条目的能力).

  • 迭代器与链表完全不同。从迭代器返回的对象不会指向下一个对象,并且这些对象不会(必需)存储在内存中。相反,它可以根据任何内部逻辑(可以但不一定基于存储的列表)一个接一个地生成对象。 (2认同)

Ale*_*nov 9

因此,对于那些想了解该讨论摘要的人。使用以下方法计算长度为5000万的生成器表达式的最终最高分:

  • len(list(gen))
  • len([_ for _ in gen])
  • sum(1 for _ in gen),
  • ilen(gen)(来自more_itertool),
  • reduce(lambda c, i: c + 1, gen, 0)

按执行性能(包括内存消耗)排序,会让您感到惊讶:

```

1:test_list.py:8:0.492 KiB

gen = (i for i in data*1000); t0 = monotonic(); len(list(gen))
Run Code Online (Sandbox Code Playgroud)

(“列表,秒”,1.9684218849870376)

2:test_list_compr.py:8:0.867 KiB

gen = (i for i in data*1000); t0 = monotonic(); len([i for i in gen])
Run Code Online (Sandbox Code Playgroud)

('list_compr,sec',2.5885991149989422)

3:test_sum.py:8:0.859 KiB

gen = (i for i in data*1000); t0 = monotonic(); sum(1 for i in gen); t1 = monotonic()
Run Code Online (Sandbox Code Playgroud)

('sum,sec',3.441088170016883)

4:more_itertools / more.py:413:1.266 KiB

d = deque(enumerate(iterable, 1), maxlen=1)

test_ilen.py:10: 0.875 KiB
gen = (i for i in data*1000); t0 = monotonic(); ilen(gen)
Run Code Online (Sandbox Code Playgroud)

('ilen,sec',9.812256851990242)

5:test_reduce.py:8:0.859 KiB

gen = (i for i in data*1000); t0 = monotonic(); reduce(lambda counter, i: counter + 1, gen, 0)
Run Code Online (Sandbox Code Playgroud)

('reduce,sec',13.436614598002052)```

因此,len(list(gen))是最频繁且消耗较少的内存

  • 你能解释一下为什么“len(list(gen))”比基于reduce的方法消耗更少的内存吗?前者创建一个涉及内存分配的新“列表”,而后者则不应该。所以我希望后者的内存效率更高。此外,内存消耗将取决于元素类型。 (2认同)
  • `len(tuple(iterable))` 可以更加高效:[Nelson Minar 的文章](https://nelsonslog.wordpress.com/2016/04/06/python3-no-len-for-iterators/) (2认同)

小智 8

关于你原来的问题,答案仍然是通常无法知道Python中迭代器的长度.

鉴于您的问题是由pysam库的应用推动的,我可以给出一个更具体的答案:我是PySAM的贡献者,并且明确的答案是SAM/BAM文件不提供对齐读取的精确计数.这个信息也不容易从BAM索引文件中获得.最好的方法是在读取多个对齐之后使用文件指针的位置估计对齐的近似数量,并根据文件的总大小进行外推.这足以实现进度条,但不是在恒定时间内计算对齐的方法.


Mic*_*ael 5

快速基准:

import collections
import itertools

def count_iter_items(iterable):
    counter = itertools.count()
    collections.deque(itertools.izip(iterable, counter), maxlen=0)
    return next(counter)

def count_lencheck(iterable):
    if hasattr(iterable, '__len__'):
        return len(iterable)

    d = collections.deque(enumerate(iterable, 1), maxlen=1)
    return d[0][0] if d else 0

def count_sum(iterable):           
    return sum(1 for _ in iterable)

iter = lambda y: (x for x in xrange(y))

%timeit count_iter_items(iter(1000))
%timeit count_lencheck(iter(1000))
%timeit count_sum(iter(1000))
Run Code Online (Sandbox Code Playgroud)

结果:

10000 loops, best of 3: 37.2 µs per loop
10000 loops, best of 3: 47.6 µs per loop
10000 loops, best of 3: 61 µs per loop
Run Code Online (Sandbox Code Playgroud)

即简单的count_iter_items是要走的路。

针对python3进行调整:

61.9 µs ± 275 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
74.4 µs ± 190 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
82.6 µs ± 164 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
Run Code Online (Sandbox Code Playgroud)