Gibt es eine effiziente Methode, um zu wissen, wie viele Elemente sich in Python im Allgemeinen in einem Iterator befinden, ohne jedes Element zu durchlaufen und zu zählen?
Gibt es eine effiziente Methode, um zu wissen, wie viele Elemente sich in Python im Allgemeinen in einem Iterator befinden, ohne jedes Element zu durchlaufen und zu zählen?
Antworten:
Nein, es ist nicht möglich.
Beispiel:
import random
def gen(n):
for i in xrange(n):
if random.randint(0, 1) == 0:
yield i
iterator = gen(10)
Die Länge von iterator
ist unbekannt, bis Sie sie durchlaufen.
def gen(): yield random.randint(0, 1)
ist unendlich, so dass Sie niemals eine Länge finden können, indem Sie sie durchlaufen.
numIters = 0 ; while iterator: numIters +=1
?
Dieser Code sollte funktionieren:
>>> iter = (i for i in range(50))
>>> sum(1 for _ in iter)
50
Obwohl jedes Element durchlaufen und gezählt wird, ist dies der schnellste Weg.
Es funktioniert auch, wenn der Iterator kein Element hat:
>>> sum(1 for _ in range(0))
0
Natürlich läuft es für immer für eine unendliche Eingabe. Denken Sie also daran, dass Iteratoren unendlich sein können:
>>> sum(1 for _ in itertools.count())
[nothing happens, forever]
Beachten Sie außerdem, dass der Iterator dadurch erschöpft ist und bei weiteren Versuchen, ihn zu verwenden, keine Elemente angezeigt werden . Dies ist eine unvermeidbare Folge des Python-Iterator-Designs. Wenn Sie die Elemente behalten möchten, müssen Sie sie in einer Liste oder etwas anderem speichern.
_
Verweis auf Perl $_
? :)
_
für eine Dummy-Variable zu verwenden, deren Wert Sie nicht interessieren.
Nein, für jede Methode müssen Sie jedes Ergebnis auflösen. Du kannst tun
iter_length = len(list(iterable))
Aber wenn Sie das auf einem unendlichen Iterator ausführen, wird dies natürlich niemals zurückkehren. Es wird auch den Iterator verbrauchen und muss zurückgesetzt werden, wenn Sie den Inhalt verwenden möchten.
Wenn Sie uns mitteilen, welches echte Problem Sie lösen möchten, finden Sie möglicherweise einen besseren Weg, um Ihr eigentliches Ziel zu erreichen.
Bearbeiten: Mit list()
wird das gesamte iterierbare Element sofort in den Speicher eingelesen, was möglicherweise unerwünscht ist. Ein anderer Weg ist zu tun
sum(1 for _ in iterable)
als eine andere Person gepostet. Dadurch wird vermieden, dass es im Speicher bleibt.
len(list(iterable))
, werden alle Daten in den Speicher geladen. Sie können verwenden : reduce(lambda x, _: x+1, iterable, 0)
. Edit: Zonda333 Code mit Summe ist auch gut.
functools.reduce
Sie können nicht (außer der Typ eines bestimmten Iterators implementiert einige spezifische Methoden, die dies ermöglichen).
Im Allgemeinen können Sie Iteratorelemente nur zählen, indem Sie den Iterator verwenden. Eine der wahrscheinlich effizientesten Möglichkeiten:
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)
(Für Python 3.x ersetzen itertools.izip
durch zip
).
sum(1 for _ in iterator)
war dies fast doppelt so schnell.
zip
Fragen : Wenn Sie weitergeben zip(counter, iterable)
, die Sie tatsächlich 1 mehr als die iterable Zählung bekommen!
Irgendwie. Sie könnten die __length_hint__
Methode überprüfen , aber seien Sie gewarnt, dass es sich (zumindest bis zu Python 3.4, wie gsnedders hilfreich hervorhebt) um ein undokumentiertes Implementierungsdetail handelt ( folgende Meldung im Thread ), das sehr gut verschwinden oder stattdessen Nasendämonen beschwören könnte.
Ansonsten nein. Iteratoren sind nur ein Objekt, das nur die next()
Methode verfügbar macht . Sie können es so oft wie nötig aufrufen und sie können eventuell erhöhen oder auch nicht StopIteration
. Glücklicherweise ist dieses Verhalten für den Codierer die meiste Zeit transparent. :) :)
Ich mag das Kardinalitätspaket dafür, es ist sehr leicht und versucht, die schnellstmögliche Implementierung zu verwenden, die je nach Iterable verfügbar ist.
Verwendung:
>>> 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
Die tatsächliche count()
Implementierung ist wie folgt:
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
Also für diejenigen, die die Zusammenfassung dieser Diskussion wissen möchten. Die endgültigen Bestnoten für die Zählung eines Generatorausdrucks mit einer Länge von 50 Millionen unter Verwendung von:
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)
, sortiert nach Ausführungsleistung (einschließlich Speicherverbrauch) werden Sie überrascht sein:
`` `
gen = (i for i in data*1000); t0 = monotonic(); len(list(gen))
('list, sec', 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)
('reduzieren, sek', 13.436614598002052) `` `
Ist len(list(gen))
also der häufigste und am wenigsten verbrauchbare Speicher
len(list(gen))
weniger Speicher verbraucht werden sollte als der auf Reduzieren basierende Ansatz? Ersteres erstellt ein neues list
, das eine Speicherzuweisung beinhaltet, während letzteres dies nicht tun sollte. Daher würde ich erwarten, dass Letzteres speichereffizienter ist. Der Speicherverbrauch hängt auch vom Elementtyp ab.
len(tuple(iterable))
kann noch effizienter sein: Artikel von Nelson Minar
Ein Iterator ist nur ein Objekt, das einen Zeiger auf das nächste Objekt hat, das von einer Art Puffer oder Stream gelesen werden soll. Es ist wie eine LinkedList, in der Sie nicht wissen, wie viele Dinge Sie haben, bis Sie sie durchlaufen. Iteratoren sollen effizient sein, da sie Ihnen lediglich anhand von Referenzen mitteilen, was als nächstes kommt, anstatt die Indizierung zu verwenden (aber wie Sie gesehen haben, verlieren Sie die Fähigkeit, zu sehen, wie viele Einträge als nächstes kommen).
In Bezug auf Ihre ursprüngliche Frage lautet die Antwort immer noch, dass es im Allgemeinen keine Möglichkeit gibt, die Länge eines Iterators in Python zu ermitteln.
Da Ihre Frage durch eine Anwendung der Pysam-Bibliothek motiviert ist, kann ich eine genauere Antwort geben: Ich bin ein Mitwirkender an PySAM und die endgültige Antwort lautet, dass SAM / BAM-Dateien keine exakte Anzahl ausgerichteter Lesevorgänge liefern. Diese Informationen sind auch nicht leicht aus einer BAM-Indexdatei verfügbar. Das Beste, was Sie tun können, ist, die ungefähre Anzahl von Ausrichtungen zu schätzen, indem Sie die Position des Dateizeigers verwenden, nachdem Sie eine Anzahl von Ausrichtungen gelesen und basierend auf der Gesamtgröße der Datei extrapoliert haben. Dies reicht aus, um einen Fortschrittsbalken zu implementieren, jedoch keine Methode zum Zählen von Ausrichtungen in konstanter Zeit.
Ein kurzer Maßstab:
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))
Die Ergebnisse:
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
Dh die einfachen count_iter_items sind der richtige Weg.
Anpassen für 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)
Es gibt zwei Möglichkeiten, die Länge von "etwas" auf einem Computer zu ermitteln.
Die erste Möglichkeit besteht darin, eine Zählung zu speichern - dies erfordert alles, was die Datei / Daten berührt, um sie zu ändern (oder eine Klasse, die nur Schnittstellen verfügbar macht -, aber es läuft auf dasselbe hinaus).
Die andere Möglichkeit besteht darin, darüber zu iterieren und zu zählen, wie groß es ist.
Dies widerspricht der Definition eines Iterators, der ein Zeiger auf ein Objekt ist, sowie Informationen darüber, wie Sie zum nächsten Objekt gelangen.
Ein Iterator weiß nicht, wie oft er bis zum Beenden iterieren kann. Dies könnte unendlich sein, also könnte Unendlichkeit Ihre Antwort sein.
Obwohl es im Allgemeinen nicht möglich ist, das zu tun, was gefragt wurde, ist es oft nützlich, zu zählen, wie viele Elemente nach dem Durchlaufen wiederholt wurden. Dafür können Sie jaraco.itertools.Counter oder ähnliches verwenden. Hier ist ein Beispiel mit Python 3 und rwt zum Laden des Pakets.
$ 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
Vermutlich möchten Sie die Anzahl der Elemente zählen, ohne sie zu durchlaufen, damit der Iterator nicht erschöpft ist und Sie ihn später erneut verwenden. Dies ist mit copy
oder möglichdeepcopy
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")
Die Ausgabe ist " Finding the length did not exhaust the iterator!
"
Optional (und nicht empfohlen) können Sie die integrierte len
Funktion wie folgt schattieren :
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
Iterator zurück, der erwartete, dass die resultierenden Funktionsaufrufe nur einmal auftreten würden.