Der sauberste Weg, um das letzte Element vom Python-Iterator zu erhalten

109

Was ist der beste Weg, um das letzte Element von einem Iterator in Python 2.6 abzurufen? Zum Beispiel sagen

my_iter = iter(range(5))

Was ist der kürzeste Code / sauberste Weg, um 4davon zu kommen my_iter?

Ich könnte das tun, aber es scheint nicht sehr effizient zu sein:

[x for x in my_iter][-1]
Peter
quelle
4
Iteratoren gehen davon aus, dass Sie die Elemente durchlaufen und nicht wirklich auf die letzten Elemente zugreifen möchten. Was hindert Sie daran, einfach Bereich (5) [- 1] zu verwenden?
Frank
7
@Frank - Ich nahm an, dass der eigentliche Iterator komplexer und / oder weiter entfernt und / oder schwerer zu kontrollieren war alsiter(range(5))
Chris Lutz
3
@Frank: Die Tatsache, dass es sich tatsächlich um eine viel kompliziertere Generatorfunktion handelt, die den Iterator liefert. Ich habe dieses Beispiel nur so erfunden, dass es einfach und klar war, was geschah.
Peter
4
Wenn Sie das letzte Element eines Iterators möchten, besteht eine große Wahrscheinlichkeit, dass Sie etwas falsch machen. Die Antwort ist jedoch, dass es keinen saubereren Weg gibt, den Iterator zu durchlaufen. Dies liegt daran, dass Iteratoren keine Größe haben und möglicherweise überhaupt nicht enden und als solche möglicherweise kein letztes Element haben. (Das heißt, Ihr Code wird natürlich für immer ausgeführt). Die verbleibende Frage lautet also: Warum möchten Sie das letzte Element eines Iterators?
Lennart Regebro
3
@ Peter: Aktualisieren Sie bitte Ihre Frage. Fügen Sie einer Frage, die Sie besitzen, keine Kommentare hinzu. Bitte aktualisieren Sie die Frage und entfernen Sie die Kommentare.
S.Lott

Antworten:

100
item = defaultvalue
for item in my_iter:
    pass
Thomas Wouters
quelle
4
Warum der Platzhalter "Standardwert"? Warum nicht None? Genau dafür Noneist es da. Schlagen Sie vor, dass ein funktionsspezifischer Standardwert sogar korrekt sein könnte? Wenn der Iterator Iterierte nicht wirklich, dann ein Out-of-Band - Wert ist mehr sinnvoll als einiger irreführenden funktionsspezifischen Standard.
S.Lott
46
Der Standardwert ist nur ein Platzhalter für mein Beispiel. Wenn Sie Noneals Standardwert verwenden möchten , haben Sie die Wahl. Keiner ist nicht immer die vernünftigste Standardeinstellung und möglicherweise nicht einmal außerhalb der Band. Persönlich neige ich dazu, 'defaultvalue = object ()' zu verwenden, um sicherzustellen, dass es sich um einen wirklich eindeutigen Wert handelt. Ich möchte nur darauf hinweisen, dass die Auswahl der Standardeinstellung außerhalb des Bereichs dieses Beispiels liegt.
Thomas Wouters
28
@ S.Lott: Vielleicht ist es nützlich, den Unterschied zwischen einem leeren Iterator und einem Iterator zu unterscheiden, Nonedessen Endwert der endgültige Wert ist
John La Rooy,
8
Gibt es einen Entwurfsfehler in allen Iteratoren aller eingebauten Containertypen? Zum ersten Mal habe ich davon gehört :)
Thomas Wouters
7
Während dies wahrscheinlich die schnellere Lösung ist, beruht sie auf der Variablen, die in for-Schleifen leckt (eine Funktion für einige, ein Fehler für andere - wahrscheinlich sind FP-Leute entsetzt). Wie auch immer, Guido sagte, dass dies immer so funktionieren wird, also ist es eine sichere Konstruktion.
tokland
68

Verwenden Sie eine dequeGröße 1.

from collections import deque

#aa is an interator
aa = iter('apple')

dd = deque(aa, maxlen=1)
last_element = dd.pop()
martin23487234
quelle
6
Dies ist tatsächlich der schnellste Weg, um eine lange Sequenz zu erschöpfen, wenn auch nur geringfügig schneller als die for-Schleife.
Sven Marnach
11
+1, um technisch korrekt zu sein, aber die Leser sollten die üblichen Python-Vorbehalte haben: "Müssen Sie dies WIRKLICH optimieren?", "Dies ist weniger explizit, was nicht Pythonic ist" und "Die schnellere Geschwindigkeit hängt von der Implementierung ab, welche kann sich ändern."
Leez
1
Es ist auch ein Erinnerungsschwein
Eelco Hoogendoorn
6
@EelcoHoogendoorn Warum ist es ein Memory-Hog, auch mit einem Maximum von 1?
Chris Wesseling
1
Von allen hier vorgestellten Lösungen finde ich, dass dies die schnellste und speichereffizienteste ist .
Markus Strauss
65

Wenn Sie Python 3.x verwenden:

*_, last = iterator # for a better understanding check PEP 448
print(last)

Wenn Sie Python 2.7 verwenden:

last = next(iterator)
for last in iterator:
    continue
print last


Randnotiz:

Normalerweise ist die oben vorgestellte Lösung die, die Sie für normale Fälle benötigen. Wenn Sie jedoch mit einer großen Datenmenge arbeiten, ist es effizienter, eine Lösung dequeder Größe 1 zu verwenden. ( Quelle )

from collections import deque

#aa is an interator
aa = iter('apple')

dd = deque(aa, maxlen=1)
last_element = dd.pop()
DhiaTN
quelle
1
@virtualxtc: Der Unterstrich ist nur eine Kennung. Der Stern vorne sagt "Liste erweitern". Je besser lesbar wäre *lst, last = some_iterable.
Pepr
4
@virtualxtc nope _ist eine spezielle Variable in Python und wird entweder verwendet, um den letzten Wert zu speichern oder um zu sagen, dass mir der Wert egal ist, sodass er bereinigt werden könnte.
DhiaTN
1
Diese Python 3-Lösung ist nicht speichereffizient.
Markus Strauss
3
@DhiaTN Ja, du hast absolut recht. Eigentlich mag ich die Python 3-Sprache, die Sie sehr gezeigt haben. Ich wollte nur klarstellen, dass es bei "Big Data" nicht funktioniert. Ich benutze dafür collection.deque, was schnell und speichereffizient ist (siehe Lösung von martin23487234).
Markus Strauss
1
Dieses py3.5 + Beispiel sollte in PEP 448 sein. Wunderbar.
EliadL
33

Wahrscheinlich eine Verwendung wert, __reversed__wenn es verfügbar ist

if hasattr(my_iter,'__reversed__'):
    last = next(reversed(my_iter))
else:
    for last in my_iter:
        pass
John La Rooy
quelle
26

So einfach wie:

max(enumerate(the_iter))[1]
Chema Cortes
quelle
8
Oh, das ist klug. Nicht die effizienteste oder lesbarste, aber cleverste.
Timgeb
6
Also nur laut nachdenken ... Das funktioniert, weil es wie folgt enumeratezurückgibt (index, value): (0, val0), (1, val1), (2, val2)... und dann standardmäßig, maxwenn eine Liste von Tupeln angegeben wird, nur mit dem ersten Wert des Tupels verglichen wird, es sei denn, zwei erste Werte sind gleich, die sie hier nie sind weil sie Indizes darstellen. Dann ist der nachfolgende Index, weil max das gesamte (idx, value) Tupel zurückgibt, während wir nur daran interessiert sind value. Interessante Idee.
Taylor Edmiston
21

Es ist unwahrscheinlich, dass dies aufgrund des Lambda schneller ist als die leere for-Schleife, aber vielleicht gibt es jemand anderem eine Idee

reduce(lambda x,y:y,my_iter)

Wenn der Iter leer ist, wird ein TypeError ausgelöst

John La Rooy
quelle
IMHO, dieser ist der direkteste, konzeptionell. Anstatt TypeErrorfür eine leere iterable zu erhöhen , können Sie auch einen Standardwert über den Anfangswert von reduce()z last = lambda iterable, default=None: reduce(lambda _, x: x, iterable, default).
Egnha
9

Da ist das

list( the_iter )[-1]

Wenn die Länge der Iteration wirklich episch ist - so lang, dass das Materialisieren der Liste den Speicher erschöpft -, müssen Sie das Design wirklich überdenken.

S.Lott
quelle
1
Dies ist die einfachste Lösung.
Laike9m
2
Leicht besser, ein Tupel zu benutzen.
Christopher Smith
9
Stimme dem letzten Satz überhaupt nicht zu. Die Arbeit mit sehr großen Datenmengen (die beim gleichzeitigen Laden die Speichergrenzen überschreiten können) ist der Hauptgrund für die Verwendung eines Iterators anstelle einer Liste.
Paul
@Paul: Einige Funktionen geben nur einen Iterator zurück. Dies ist in diesem Fall eine kurze und gut lesbare Methode (für nicht-epische Listen).
serv-inc
Das ist der am wenigsten effiziente Weg, den man als schlechte schlechte schlechte Angewohnheit vermeiden sollte. Eine andere Möglichkeit besteht darin, sort (sequence) [- 1] zu verwenden, um das maximale Element der Sequenz zu erhalten. Bitte verwenden Sie diese kranken Muster niemals, wenn Sie Softwareentwickler werden möchten.
Maksym Ganenko
5

ich würde ... benutzen reversed , außer dass es nur Sequenzen anstelle von Iteratoren braucht, was ziemlich willkürlich erscheint.

Wie auch immer Sie es tun, Sie müssen den gesamten Iterator durchlaufen. Wenn Sie den Iterator bei maximaler Effizienz nie wieder benötigen, können Sie einfach alle Werte in den Papierkorb werfen:

for last in my_iter:
    pass
# last is now the last item

Ich denke jedoch, dass dies eine suboptimale Lösung ist.

Chris Lutz
quelle
4
reverse () benötigt keinen Iterator, sondern nur Sequenzen.
Thomas Wouters
3
Es ist überhaupt nicht willkürlich. Die einzige Möglichkeit, einen Iterator umzukehren, besteht darin, bis zum Ende zu iterieren und dabei alle Elemente im Speicher zu belassen. Ich, e, Sie müssen zuerst eine Sequenz daraus machen, bevor Sie sie umkehren können. Was natürlich den Zweck des Iterators in erster Linie zunichte macht und auch bedeuten würde, dass Sie plötzlich ohne ersichtlichen Grund viel Speicherplatz verbrauchen. Es ist also das Gegenteil von willkürlich. :)
Lennart Regebro
@ Lennart - Als ich willkürlich sagte, meinte ich nervig. Ich konzentriere meine Sprachkenntnisse auf mein Papier, das in ein paar Stunden um diese Zeit morgens fällig ist.
Chris Lutz
3
Meinetwegen. Obwohl IMO, wäre es ärgerlicher, wenn es Iteratoren akzeptieren würde, da fast jede Verwendung davon eine schlechte Idee (tm) wäre. :)
Lennart Regebro
3

Die Toolz- Bibliothek bietet eine schöne Lösung:

from toolz.itertoolz import last
last(values)

Das Hinzufügen einer Nicht-Kern-Abhängigkeit lohnt sich jedoch möglicherweise nicht, wenn Sie sie nur in diesem Fall verwenden.

lumbrisch
quelle
0

Ich würde nur verwenden next(reversed(myiter))

thomas.mac
quelle
8
TypeError: Argument zu reverse () muss eine Sequenz sein
Labo
0

Die Frage betrifft das Abrufen des letzten Elements eines Iterators. Wenn Ihr Iterator jedoch durch Anwenden von Bedingungen auf eine Sequenz erstellt wird, kann umgekehrt verwendet werden, um das "Erste" einer umgekehrten Sequenz zu finden, wobei nur die erforderlichen Elemente durch Anwenden betrachtet werden in umgekehrter Reihenfolge.

Ein erfundenes Beispiel,

>>> seq = list(range(10))
>>> last_even = next(_ for _ in reversed(seq) if _ % 2 == 0)
>>> last_even
8
Wyrmwood
quelle
0

Alternativ können Sie für unendliche Iteratoren Folgendes verwenden:

from itertools import islice 
last = list(islice(iterator(), 1000))[-1] # where 1000 is number of samples 

Ich dachte, es wäre dann langsamer, dequeaber es ist genauso schnell und tatsächlich schneller als bei der Loop-Methode (irgendwie)

qocu
quelle
-6

Die Frage ist falsch und kann nur zu einer komplizierten und ineffizienten Antwort führen. Um einen Iterator zu erhalten, gehen Sie natürlich von etwas aus, das iterierbar ist und in den meisten Fällen eine direktere Möglichkeit bietet, auf das letzte Element zuzugreifen.

Sobald Sie einen Iterator aus einem Iterator erstellt haben, müssen Sie die Elemente nicht mehr durchgehen, da dies das einzige ist, was ein Iterator bietet.

Der effizienteste und klarste Weg besteht also darin, nicht zuerst den Iterator zu erstellen, sondern die nativen Zugriffsmethoden der Iterable zu verwenden.

ludwig
quelle
5
Wie würden Sie die letzte Zeile einer Datei erhalten?
Brice M. Dempsey
@ BriceM.Dempsey Der beste Weg ist nicht, die gesamte (möglicherweise riesige) Datei zu durchlaufen, sondern die Dateigröße minus 100 zu ermitteln, die letzten 100 Bytes zu lesen und nach einer neuen Zeile zu suchen. Wenn keine vorhanden ist, gehen Sie Weitere 100 Bytes usw. zurücksetzen. Je nach Szenario können Sie auch die Schritt-zurück-Größe erhöhen. Das Lesen von Millionen Zeilen ist definitiv eine nicht optimale Lösung.
Alfe