Länge der Generatorleistung [Duplicate]
Frage
Diese Frage bereits eine Antwort hier:
Python bietet eine schöne Methode für das Erhalten Länge eines eifrigen iterable, len(x)
das ist. Aber ich kann nichts Vergleichbares für faules Iterables durch den Generator Comprehensions und Funktionen dargestellt finden. Natürlich ist es nicht schwer, so etwas wie zu schreiben:
def iterlen(x):
n = 0
try:
while True:
next(x)
n += 1
except StopIteration: pass
return n
Aber ich kann nicht von einem Gefühl loswerden, dass ich ein Fahrrad bin Neuimplementierung.
(Während ich die Funktion tippte, ein Gedanke kam meiner Meinung nach: vielleicht gibt es wirklich keine solche Funktion, weil sie „zerstört“ sein Argument nicht ein Problem für meinen Fall, though.)
. P. S .: über die ersten Antworten - ja, so etwas wie len(list(x))
auch funktionieren würde, aber das erhöht drastisch die Nutzung von Speichern
P.P.S .: nachgeprüften ... Ignorieren Sie die P. S., scheint, dass ich einen Fehler gemacht, während das versucht, es funktioniert gut. Sorry für die Mühe.
Lösung
Es gibt nicht ein, weil man es im allgemeinen Fall nicht tun können - was, wenn Sie einen fauler unendlich Generator? Zum Beispiel:
def fib():
a, b = 0, 1
while True:
a, b = b, a + b
yield a
Damit endet nie aber wird die Fibonacci-Zahlen erzeugen. Sie können so viele Fibonacci-Zahlen erhalten, wie Sie durch den Aufruf next()
wollen.
Wenn Sie wirklich die Anzahl der Elemente müssen wissen, es gibt, dann kann man nicht durch sie durchlaufen linear einmal sowieso, also nur eine andere Datenstruktur verwenden, wie zum Beispiel eine regelmäßige Liste.
Andere Tipps
Der einfachste Weg ist wahrscheinlich nur sum(1 for _ in gen)
wo gen Ihr Generator ist.
def count(iter):
return sum(1 for _ in iter)
Oder noch besser:
def count(iter):
try:
return len(iter)
except TypeError:
return sum(1 for _ in iter)
Wenn es nicht durchsuchbar ist, wird es eine TypeError
werfen.
Oder, wenn Sie bestimmte im Generator etwas zählen:
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)
Also, für diejenigen, die die Zusammenfassung dieser Diskussion wissen möchten. Die endgültigen Top-Noten für einen 50 Millionen-lengthed Generator Ausdruck Zählen mit:
-
len(list(gen))
, -
len([_ for _ in gen])
, -
sum(1 for _ in gen),
-
ilen(gen)
(von more_itertool ) -
reduce(lambda c, i: c + 1, gen, 0)
,
nach Leistung der Ausführung sortiert (einschließlich Speicherverbrauch), werden Sie überrascht:
`` `
1: test_list.py:8: 0.492 KiB
gen = (i for i in data*1000); t0 = monotonic(); len(list(gen))
(Liste, s ', 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])
( 'list_compr, s', 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()
( 'Summe, s', 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)
( 'ilen, s', 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)
( 'reduzieren, s', 13,436614598002052) `` `
Also, len(list(gen))
ist die häufigste und weniger Speicher Verbrauch
Sie enumerate () in einer Schleife durch den erzeugten Datenstrom verwenden können, dann die letzte Nummer zurück -. Die Anzahl der Elemente
Ich habe versucht, itertools.count () mit itertools.izip (), aber ohne Glück zu verwenden. Dies ist die beste / kürzeste Antwort, die ich gekommen bin oben mit:
#!/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-Lösung ist viel besser:
def count_iterable(i):
return sum(1 for e in i)
Verwenden Sie reduzieren (Funktion, iterable [, initializer]) eine speichereffiziente rein funktionale Lösung:
>>> iter = "This string has 30 characters."
>>> reduce(lambda acc, e: acc + 1, iter, 0)
30
Per Definition nur eine Teilmenge von Generatoren wird nach einer bestimmten Anzahl von Argumenten zurück (eine vordefinierte Länge), und selbst dann nur eine Teilmenge dieser endlichen Generatoren haben ein vorhersagbares Ende (den Generator Zugriff Seite haben kann -Effekte, die den Generator früher aufhören können).
Wenn Sie möchten Länge Methoden für Ihren Generator implementieren, müssen Sie zunächst definieren, was Sie die „Länge“ betrachten (es ist die Gesamtzahl der Elemente? Die Anzahl der verbleibenden Elemente?), Dann wickeln Sie Ihren Generator in einer Klasse . Hier ein Beispiel:
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
Hier ist, wie es zu benutzen:
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]
Versuchen Sie, die more_itertools
Paket für eine einfache Lösung. Beispiel:
>>> import more_itertools
>>> it = iter("abcde") # sample generator
>>> it
<str_iterator at 0x4ab3630>
>>> more_itertools.ilen(it)
5
Siehe diesen Beitrag für ein weiteres Beispiel angewendet wird.
Dies ist ein Hack, aber wenn Sie wirklich len
Arbeit haben sich auf eine allgemeine iterable (raubend es in der Art und Weise) möchten, können Sie Ihre eigene Version von len
erstellen.
Die len
Funktion entspricht im Wesentlichen die folgenden (obwohl Implementierungen in der Regel einige Optimierungen bieten die zusätzliche Lookup zu vermeiden):
def len(iterable):
return iterable.__len__()
Deshalb können wir unsere new_len
definieren, das zu versuchen, und wenn __len__
nicht vorhanden ist, die Anzahl der Elemente selbst zählen durch den Verzehr der durchsuchbar:
def new_len(iterable):
try:
return iterable.__len__()
except AttributeError:
return sum(1 for _ in iterable)
Die oben genannten Arbeiten in Python 3.2 und (soweit ich weiß) soll jede erdenkliche Art von iterable abdecken.