Length of generator output
Python提供了一种很好的方法来获取渴望的可迭代
1 2 3 4 5 6 7 8 | def iterlen(x): n = 0 try: while True: next(x) n += 1 except StopIteration: pass return n |
但是我无法摆脱正在重新装备自行车的感觉。
(虽然我正在键入该函数,但我的脑海中浮现了一个念头:也许确实没有这样的函数,因为它"破坏了"其参数。不过,对于我的情况而言,这不是问题)。
P.S .:关于第一个答案-是的,类似
P.P.S .:重新检查...忽略P.S.,似乎我在尝试时出错,它可以正常工作。 抱歉,添麻烦了。
最简单的方法可能只是
没有一个是因为您通常无法做到这一点-如果您有一个惰性无限生成器怎么办?例如:
1 2 3 4 5 | def fib(): a, b = 0, 1 while True: a, b = b, a + b yield a |
这永远不会终止,但会生成斐波那契数。您可以通过调用
如果您确实需要知道项目的数量,那么无论如何都无法一次线性地遍历它们,因此只能使用其他数据结构,例如常规列表。
1 2 | def count(iter): return sum(1 for _ in iter) |
或者更好:
1 2 3 4 5 | def count(iter): try: return len(iter) except TypeError: return sum(1 for _ in iter) |
如果不是可迭代的,它将抛出一个
或者,如果您想计算生成器中特定的内容:
1 2 3 4 5 6 7 8 9 | def count(iter, key=None): if key: if callable(key): return sum(bool(key(x)) for x in iter) return sum(x == key for x in iter) try: return len(iter) except TypeError: return sum(1 for _ in iter) |
因此,对于那些想了解该讨论摘要的人。使用以下方法计算长度为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
1 | gen = (i for i in data*1000); t0 = monotonic(); len(list(gen)) |
("列表,秒",1.9684218849870376)
2:test_list_compr.py:8:0.867 KiB
1 | gen = (i for i in data*1000); t0 = monotonic(); len([i for i in gen]) |
('list_compr,sec',2.5885991149989422)
3:test_sum.py:8:0.859 KiB
1 | gen = (i for i in data*1000); t0 = monotonic(); sum(1 for i in gen); t1 = monotonic() |
('sum,sec',3.441088170016883)
4:more_itertools / more.py:413:1.266 KiB
1 2 3 4 | 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)
5:test_reduce.py:8:0.859 KiB
1 | gen = (i for i in data*1000); t0 = monotonic(); reduce(lambda counter, i: counter + 1, gen, 0) |
('reduce,sec',13.436614598002052)
```
因此,
您可以使用enumerate()遍历生成的数据流,然后返回最后一个数字-项目数。
我尝试将itertools.count()与itertools.izip()一起使用,但没有运气。这是我提出的最好/最短的答案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #!/usr/bin/python import itertools def func(): for i in 'yummy beer': yield i def icount(ifunc): size = -1 # for the case of an empty iterator for size, _ in enumerate(ifunc()): pass return size + 1 print list(func()) print 'icount', icount(func) # ['y', 'u', 'm', 'm', 'y', ' ', 'b', 'e', 'e', 'r'] # icount 10 |
Kamil Kisiel的解决方案更好:
1 2 | def count_iterable(i): return sum(1 for e in i) |
使用reduce(function,iterable [,initializer])获得内存有效的纯函数解决方案:
1 2 3 | >>> iter ="This string has 30 characters." >>> reduce(lambda acc, e: acc + 1, iter, 0) 30 |
尝试使用
1 2 3 4 5 6 7 8 | >>> import more_itertools >>> it = iter("abcde") # sample generator >>> it <str_iterator at 0x4ab3630> >>> more_itertools.ilen(it) 5 |
有关另一个应用示例,请参见本文。
根据定义,只有一定数量的参数(具有预定义的长度)后,才会生成器的子集返回,即使这样,这些有限生成器中只有一个子集具有可预测的结局(访问生成器会产生副作用,即可以更早停止发电机)。
如果要为生成器实现length方法,则必须首先定义您认为的"长度"(是元素的总数还是剩余元素的数量?),然后将生成器包装在一个类中。这是一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | class MyFib(object): """ A class iterator that iterates through values of the Fibonacci sequence, until, optionally, a maximum length is reached. """ def __init__(self, length): self._length = length self._i = 0 def __iter__(self): a, b = 0, 1 while not self._length or self._i < self._length: a, b = b, a + b self._i += 1 yield a def __len__(self): "This method returns the total number of elements" if self._length: return self._length else: raise NotImplementedError("Infinite sequence has no length") # or simply return None / 0 depending # on implementation |
使用方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | In [151]: mf = MyFib(20) In [152]: len(mf) Out[152]: 20 In [153]: l = [n for n in mf] In [154]: len(l) Out[154]: 20 In [155]: l Out[155]: [1, 1, 2, ... 6765] In [156]: mf0 = MyFib(0) In [157]: len(mf0) --------------------------------------------------------------------------- NotImplementedError Traceback (most recent call last) <ipython-input-157-2e89b32ad3e4> in <module>() ----> 1 len(mf0) /tmp/ipython_edit_TWcV1I.py in __len__(self) 22 return self._length 23 else: ---> 24 raise NotImplementedError 25 # or simply return None / 0 depending 26 # on implementation NotImplementedError: In [158]: g = iter(mf0) In [159]: l0 = [g.next(), g.next(), g.next()] In [160]: l0 Out[160]: [1, 1, 2] |
这是一个技巧,但是如果您真的想让
1 2 | def len(iterable): return iterable.__len__() |
因此,我们可以定义
1 2 3 4 5 | def new_len(iterable): try: return iterable.__len__() except AttributeError: return sum(1 for _ in iterable) |
上面的代码在Python 2/3中有效,并且(据我所知)应该涵盖所有可能的迭代类型。