Wenn ich die Anzahl der Elemente in einem Iterable haben möchte, ohne mich um die Elemente selbst zu kümmern, wie würde ich das pythonisch erreichen? Im Moment würde ich definieren
def ilen(it):
return sum(itertools.imap(lambda _: 1, it)) # or just map in Python 3
aber ich verstehe, dass lambda
es fast als schädlich angesehen wird und lambda _: 1
sicherlich nicht schön ist.
(Der Anwendungsfall hierfür ist das Zählen der Anzahl der Zeilen in einer Textdatei, die einem regulären Ausdruck entsprechen, d grep -c
. H. )
_
als Variablennamen, da (1) er die Leute verwirrt und sie glauben lässt, dass dies eine spezielle Syntax ist, (2)_
im interaktiven Interpreter kollidiert und (3) mit dem allgemeinen gettext-Alias kollidiert ._
ganze Zeit für nicht verwendete Variablen (eine Gewohnheit aus der Prolog- und Haskell-Programmierung). (1) ist ein Grund, dies überhaupt zu fragen. Ich habe (2) und (3) nicht berücksichtigt, danke, dass Sie darauf hingewiesen haben!Antworten:
Aufrufe von
itertools.imap()
in Python 2 odermap()
in Python 3 können durch entsprechende Generatorausdrücke ersetzt werden:sum(1 for dummy in it)
Dies verwendet auch einen Lazy-Generator, sodass keine vollständige Liste aller Iteratorelemente im Speicher erstellt wird.
quelle
len(list(it))
- oder wenn die Elemente eindeutig sindlen(set(it))
, um ein Zeichen zu speichern.len(list(it))
ist in den meisten Fällen in Ordnung. Wenn Sie jedoch einen faulen Iterator haben, der viele, viele Elemente liefert, möchten Sie nicht alle gleichzeitig im Speicher speichern, um sie zu zählen. Dies wird vermieden, wenn Sie den Code in dieser Antwort verwenden.sum(1 for _ in generator)
vermieden, den Speicher zu füllen.Methode, die bedeutend schneller ist als
sum(1 for i in it)
wenn die Iterable lang sein kann (und nicht bedeutend langsamer, wenn die Iterable kurz ist), während das Verhalten des festen Speicher-Overheads beibehalten wird (im Gegensatz zulen(list(it))
), um Swap-Thrashing und Neuzuweisungs-Overhead für größere Eingaben zu vermeiden:# On Python 2 only, get zip that lazily generates results instead of returning list from future_builtins import zip from collections import deque from itertools import count def ilen(it): # Make a stateful counting iterator cnt = count() # zip it with the input iterator, then drain until input exhausted at C level deque(zip(it, cnt), 0) # cnt must be second zip arg to avoid advancing too far # Since count 0 based, the next value is the count return next(cnt)
Wie
len(list(it))
es führt die Schleife in C - Code auf CPython (deque
,count
undzip
sind in C alle implementiert); Das Vermeiden der Ausführung von Bytecode pro Schleife ist normalerweise der Schlüssel zur Leistung in CPython.Es ist überraschend schwierig, faire Testfälle für den Leistungsvergleich zu finden (
list
Cheats,__length_hint__
die für Iterables mit beliebiger Eingabe wahrscheinlich nicht verfügbar sind,itertools
Funktionen, die nicht bereitgestellt werden , verfügen__length_hint__
häufig über spezielle Betriebsmodi, die schneller arbeiten, wenn der Wert in jeder Schleife zurückgegeben wird wird freigegeben / freigegeben, bevor der nächste Wert angefordert wird, wasdeque
mitmaxlen=0
will do). Der Testfall, den ich verwendet habe, bestand darin, eine Generatorfunktion zu erstellen, die eine Eingabe übernimmt und einen Generator auf C-Ebene zurückgibt, dem spezielleitertools
Optimierungen für Rückgabecontainer fehlten, oder__length_hint__
Python 3.3 zu verwendenyield from
:def no_opt_iter(it): yield from it
Verwenden Sie dann
ipython
%timeit
Magie (ersetzen Sie 100 durch verschiedene Konstanten):>>> %%timeit -r5 fakeinput = (0,) * 100 ... ilen(no_opt_iter(fakeinput))
Wenn die Eingabe nicht groß genug ist,
len(list(it))
um Speicherprobleme zu verursachen, dauert meine Lösung auf einer Linux-Box mit Python 3.5 x64 etwa 50% länger alsdef ilen(it): return len(list(it))
unabhängig von der Eingabelänge.Für die kleinsten Eingänge bedeuten die Einrichtungskosten für das Aufrufen von
deque
/zip
/count
/,next
dass es auf diese Weise unendlich länger dauert alsdef ilen(it): sum(1 for x in it)
(ungefähr 200 ns mehr auf meinem Computer für einen Eingang der Länge 0, was einer Steigerung von 33% gegenüber dem einfachensum
Ansatz entspricht), aber für Bei längeren Eingaben dauert es ungefähr die Hälfte der Zeit pro zusätzlichem Element. Bei Eingaben der Länge 5 sind die Kosten gleich, und irgendwo im Bereich der Länge 50-100 ist der anfängliche Overhead im Vergleich zur tatsächlichen Arbeit nicht wahrnehmbar. Dersum
Ansatz dauert ungefähr doppelt so lange.Grundsätzlich sollten Sie diese Lösung verwenden, wenn die Speichernutzung oder Eingaben keine begrenzte Größe haben und Sie mehr Wert auf Geschwindigkeit als auf Kürze legen. Wenn Eingaben begrenzt und klein sind,
len(list(it))
ist dies wahrscheinlich am besten, und wenn sie unbegrenzt sind, aber Einfachheit / Kürze zählt, würden Sie verwendensum(1 for x in it)
.quelle
more_itertools.ilen
.maxlen
nach Schlüsselwörtern und nicht nach Position übergeben wurden), aber das ist ein fester Overhead, der für die Big-O-Laufzeit nicht von Bedeutung ist. So oder so haben sie mich kopiert (ich habe das vor 3,5 Jahren gepostet), nicht umgekehrt. :-)sum(1 ..)
,len(list())
etc. ) auf die besondere Situation.Ein kurzer Weg ist:
def ilen(it): return len(list(it))
Wenn Sie viele Elemente generieren (z. B. Zehntausende oder mehr), kann das Einfügen in eine Liste zu einem Leistungsproblem werden. Dies ist jedoch ein einfacher Ausdruck der Idee, bei der die Leistung in den meisten Fällen keine Rolle spielt.
quelle
sum(1 for i in it)
solange alles in den Speicher passt.len(it)
funktioniert nicht.sum(it)
,max(it)
,min(it)
Und so weiter wie erwartet, nurlen(it)
nicht.it
es sich um einen Iterator handelt, gibt es keine Garantie dafür, dass er seine eigene Länge kennt, ohne sie zu verlieren . Das offensichtlichste Beispiel sind Dateiobjekte. Sie haben eine Länge, die auf der Anzahl der Zeilen in der Datei basiert. Die Zeilen haben jedoch eine variable Länge. Die einzige Möglichkeit, die Anzahl der Zeilen zu ermitteln, besteht darin, die gesamte Datei zu lesen und die Zeilenumbrüche zu zählen.len()
soll eine billigeO(1)
Operation sein; Möchten Sie, dass Dateien mit mehreren GB stillschweigend gelesen werden, wenn Sie nach ihrer Länge fragen?sum
,max
Undmin
sind Aggregationsfunktionen , die ihre Daten lesen müssen,len
ist es nicht.count(it)
.more_itertools
ist eine Bibliothek eines Drittanbieters, die einilen
Tool implementiert .pip install more_itertools
import more_itertools as mit mit.ilen(x for x in range(10)) # 10
quelle
more_itertools
sie viele andere Dinge enthält. Ich möchte sie nur notieren.)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
quelle
Dies wären meine Entscheidungen, entweder die eine oder die andere:
print(len([*gen])) print(len(list(gen)))
quelle
list
. Dies bedeutet, dass diese Antwort gegenüber anderen Antworten keinen Mehrwert bietet, es sei denn, Sie können erklären, warum die erste Option einen Wert hat.len([*gen])
ist ziemlich kurz. Dies wäre beispielsweise in Code Golf wertvoll. Ich stimme Ihnen jedoch zu, dass diese Lösung in den meisten Anwendungsfällen nicht optimal ist.len([*gen])
fühlt sich für mich unpythonisch an.