通常,是否有一种有效的方法可以知道Python的迭代器中有多少个元素,而无需遍历每个元素并进行计数?
通常,是否有一种有效的方法可以知道Python的迭代器中有多少个元素,而无需遍历每个元素并进行计数?
Answers:
不行,不可能
例:
import random
def gen(n):
for i in xrange(n):
if random.randint(0, 1) == 0:
yield i
iterator = gen(10)
的长度iterator
未知,直到您遍历为止。
def gen(): yield random.randint(0, 1)
是无限的,所以您将永远无法通过迭代来找到长度。
numIters = 0 ; while iterator: numIters +=1
?
此代码应工作:
>>> iter = (i for i in range(50))
>>> sum(1 for _ in iter)
50
尽管它确实遍历每个项目并计算它们,但这是最快的方法。
当迭代器没有项目时,它也适用:
>>> sum(1 for _ in range(0))
0
当然,它会无限输入地永远运行,因此请记住,迭代器可以是无限的:
>>> sum(1 for _ in itertools.count())
[nothing happens, forever]
另外,请注意,执行此操作将耗尽迭代器,并且进一步尝试使用它将看不到任何元素。这是Python迭代器设计不可避免的结果。如果要保留元素,则必须将它们存储在列表或其他内容中。
_
Perl的参考$_
吗?:)
_
用于您不需要关心其值的虚拟变量。
不,任何方法都将要求您解决所有结果。你可以做
iter_length = len(list(iterable))
但是在无限迭代器上运行该函数当然永远不会返回。它还将消耗迭代器,并且如果要使用其内容,则需要将其重置。
告诉我们您要解决的实际问题可能会帮助我们找到实现目标的更好方法。
编辑:使用list()
将立即将整个可迭代对象读取到内存中,这可能是不可取的。另一种方法是
sum(1 for _ in iterable)
如另一个人所张贴。这样可以避免将其保存在内存中。
len(list(iterable))
它将所有数据加载到内存。您可以使用:reduce(lambda x, _: x+1, iterable, 0)
。编辑:Zonda333代码总和也不错。
functools.reduce
您不能(除非特定迭代器的类型实现了某些特定方法才能实现)。
通常,您只能通过使用迭代器来计数迭代器项目。可能是最有效的方法之一:
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)
(对于Python 3.x,请替换itertools.izip
为zip
)。
sum(1 for _ in iterator)
,速度几乎快一倍。
zip
事项:如果你通过zip(counter, iterable)
,你就会得到1以上的迭代次数!
金田 您可以检查该__length_hint__
方法,但要警告(至少gsnedders指出,至少在Python 3.4之前),这是一个未记录的实现细节(遵循线程中的消息),很可能消失或召唤鼻恶魔。
否则,不会。迭代器只是一个仅公开next()
方法的对象。您可以根据需要多次调用它,它们最终可能会也可能不会出现StopIteration
。幸运的是,这种行为在大多数情况下对编码员是透明的。:)
__length_hint__
现在已记录在案,但这仅是提示,并不保证准确性。
我喜欢基数软件包,它非常轻巧,并根据可迭代性尝试使用可能的最快实现。
用法:
>>> 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
实际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
因此,对于那些想了解该讨论摘要的人。使用以下方法计算长度为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)
, 按执行性能(包括内存消耗)排序,会让您感到惊讶:
```
gen = (i for i in data*1000); t0 = monotonic(); len(list(gen))
(“列表,秒”,1.9684218849870376)
gen = (i for i in data*1000); t0 = monotonic(); len([i for i in gen])
('list_compr,sec',2.5885991149989422)
gen = (i for i in data*1000); t0 = monotonic(); sum(1 for i in gen); t1 = monotonic()
('sum,sec',3.441088170016883)
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)
('ilen,sec',9.812256851990242)
gen = (i for i in data*1000); t0 = monotonic(); reduce(lambda counter, i: counter + 1, gen, 0)
('reduce,sec',13.436614598002052)```
因此,len(list(gen))
是最频繁且消耗较少的内存
len(list(gen))
比基于减少的方法消耗更少的内存吗?前者创建一个list
涉及内存分配的新内容,而后者则不涉及。因此,我希望后者可以提高内存效率。同样,内存消耗将取决于元素类型。
len(tuple(iterable))
效率更高:Nelson Minar
迭代器只是一个对象,该对象具有指向要由某种缓冲区或流读取的下一个对象的指针,就像一个LinkedList,在其中迭代之前,您不知道自己拥有多少东西。迭代器之所以具有效率,是因为它们所做的只是告诉您引用之后是什么,而不是使用索引(但是如您所见,您失去了查看下一步有多少项的能力)。
快速基准:
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))
结果:
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
即简单的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)
有两种方法可以获取计算机上“某物”的长度。
第一种方法是存储计数-这需要接触文件/数据的任何东西来修改它(或仅公开接口的类-但归结为同一件事)。
另一种方法是遍历它并计算它的大小。
这违反了迭代器的定义,迭代器是指向对象的指针,外加有关如何到达下一个对象的信息。
迭代器不知道在终止之前它将可以迭代多少次。这可能是无限的,所以无限可能是您的答案。
尽管通常不可能执行所要求的操作,但在对项目进行迭代之后,对迭代的项目数进行计数通常仍然有用。为此,您可以使用jaraco.itertools.Counter或类似的名称。这是一个使用Python 3和rwt加载程序包的示例。
$ rwt -q jaraco.itertools -- -q
>>> import jaraco.itertools
>>> items = jaraco.itertools.Counter(range(100))
>>> _ = list(counted)
>>> items.count
100
>>> import random
>>> def gen(n):
... for i in range(n):
... if random.randint(0, 1) == 0:
... yield i
...
>>> items = jaraco.itertools.Counter(gen(100))
>>> _ = list(counted)
>>> items.count
48
大概是,您希望不迭代地对项目数进行计数,以使迭代器不会耗尽,以后再使用它。可以通过copy
或deepcopy
import copy
def get_iter_len(iterator):
return sum(1 for _ in copy.copy(iterator))
###############################################
iterator = range(0, 10)
print(get_iter_len(iterator))
if len(tuple(iterator)) > 1:
print("Finding the length did not exhaust the iterator!")
else:
print("oh no! it's all gone")
输出为“Finding the length did not exhaust the iterator!
”
您可以选择(并且不建议使用)隐藏内置len
函数,如下所示:
import copy
def len(obj, *, len=len):
try:
if hasattr(obj, "__len__"):
r = len(obj)
elif hasattr(obj, "__next__"):
r = sum(1 for _ in copy.copy(obj))
else:
r = len(obj)
finally:
pass
return r
map
迭代器,期望生成的函数调用仅发生一次。