Holen Sie sich das n-te Element eines Generators in Python


70

Gibt es eine syntaktisch präzisere Art, Folgendes zu schreiben?

gen = (i for i in xrange(10))
index = 5
for i, v in enumerate(gen):
    if i is index:
        return v

Es scheint fast natürlich, dass ein Generator einen gen[index]Ausdruck haben sollte, der als Liste fungiert, aber funktional mit dem obigen Code identisch ist.


12
Sie wollen nicht isin dieser Situation (oder in vielen Situationen überhaupt). isdient zum Vergleich der Identität, nicht der Gleichheit. Du willst ==. Dies wird in diesem Fall wahrscheinlich funktionieren, jedoch nur durch Zufall und Implementierungsdetails.
Mike Graham

1
Wie könnte es nicht funktionieren, da ich ganze Zahlen verwende? Ist es überhaupt eine gute Praxis, zu erwarten, dass das indexObjekt __eq__in solchen Fällen implementiert wird? (Dies kommt vom Thema ab ...)
Oliver Zheng

2
Versuchen 1000 is 500 + 500Sie es (wahrscheinlich) False. Siehe zum Beispiel stackoverflow.com/questions/306313/…
Scott Griffiths

2
+1 für diese Frage. Es scheint seltsam, dass es keinen weniger ausführlichen Weg gibt, "das n-te Ergebnis von gen" zu sagen.
LarsH

Eine andere Möglichkeit sind Reißverschlüsse - sie behandeln beliebige Bäume, aber eine Liste ist auch ein Baum. Siehe diese Implementierung github.com/trivio/zipper/blob/master/tests/test_zipper.py
Reb.Cabin

Antworten:


70

Eine Methode wäre zu verwenden itertools.islice

>>> gen = (x for x in range(10))
>>> index = 5
>>> next(itertools.islice(gen, index, None))
5

15

Sie können dies am countBeispiel eines Generators tun :

from itertools import islice, count
next(islice(count(), n, n+1))

Welche Version von Python ist das? Der obige Code gibt mir den Fehler AttributeError: 'itertools.islice' object has no attribute 'next'in 3.3.
LarsH

Ändern Sie nextin Python 3x zu __next__(), dhislice(count, n, n=1).__next__()
Mohammed

2
Also ist es besser zu benutzen next(islice(count(), n, n+1)).
Frozen Flame

Ich denke, Sie können die Obergrenze loswerden, dh next(islice(count(), n, None)).
user76284

7

Ich denke, der beste Weg ist:

next(x for i,x in enumerate(it) if i==n)

(Wo itist dein Iterator und nist der Index)

Sie müssen weder einen Import hinzufügen (wie bei den verwendeten Lösungen itertools) noch alle Elemente des Iterators gleichzeitig in den Speicher laden (wie bei den verwendeten Lösungen list).

Hinweis 1: Diese Version gibt einen StopIterationFehler aus, wenn Ihr Iterator weniger als n Elemente enthält. Wenn Sie Nonestattdessen erhalten möchten , können Sie verwenden:

next((x for i,x in enumerate(it) if i==n), None)

Hinweis 2: Der Anruf an enthält keine Klammern next. Dies ist kein Listenverständnis, sondern ein Generatorverständnis, das den ursprünglichen Iterator nicht weiter als bis zu seinem n-ten Element verbraucht.


Ich glaube, dies wird den gesamten Iterator durchlaufen, was es langsam macht, wenn der Iterator lange dauert.
Ubershmekel

1
@ubershmekel: Nein, wird es nicht! Es wird (natürlich) die ersten n Elemente durchlaufen, und das ist alles. Warum versuchst du es nicht selbst?
Lovasoa

1
Ich fügte als zweites eine Notiz hinzu, um deutlich zu machen, dass der ursprüngliche Iterator nicht vollständig verbraucht ist
lovasoa

5

Ich würde gegen die Versuchung argumentieren, Generatoren wie Listen zu behandeln. Der einfache, aber naive Ansatz ist der einfache Einzeiler:

gen = (i for i in range(10))
list(gen)[3]

Aber denken Sie daran, Generatoren sind keine Listen. Sie speichern ihre Zwischenergebnisse nirgendwo, sodass Sie nicht rückwärts gehen können. Ich werde das Problem anhand eines einfachen Beispiels in der Python-Antwort demonstrieren:

>>> gen = (i for i in range(10))
>>> list(gen)[3]
3
>>> list(gen)[3]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: list index out of range

Sobald Sie einen Generator durchlaufen, um den n-ten Wert in der Sequenz zu erhalten, befindet sich der Generator jetzt in einem anderen Zustand. Wenn Sie versuchen, den n-ten Wert erneut abzurufen, erhalten Sie ein anderes Ergebnis, das wahrscheinlich zu einem Fehler in Ihrem führt Code.

Schauen wir uns ein anderes Beispiel an, das auf dem Code aus der Frage basiert.

Man würde zunächst erwarten, dass das Folgende 4zweimal gedruckt wird.

gen = (i for i in range(10))
index = 4
for i, v in enumerate(gen):
    if i == index:
        answer = v
        break
print(answer)
for i, v in enumerate(gen):
    if i == index:
        answer = v
        break
print(answer)

aber tippe dies in die Antwort und du erhältst:

>>> gen = (i for i in range(10))
>>> index = 4
>>> for i, v in enumerate(gen):
...     if i == index:
...             answer = v
...             break
... 
>>> print(answer)
4
>>> for i, v in enumerate(gen):
...     if i == index:
...             answer = v
...             break
... 
>>> print(answer)
9

Viel Glück beim Aufspüren dieses Fehlers.

BEARBEITEN:

Wenn der Generator unendlich lang ist, können Sie ihn nicht einmal in eine Liste konvertieren. Der Ausdruck list(gen)wird niemals enden.

Es gibt eine Möglichkeit, einen träge ausgewerteten Caching-Wrapper um einen unendlichen Generator zu legen, damit er wie eine unendlich lange Liste aussieht, in die Sie nach Belieben indizieren können. Dies verdient jedoch eine eigene Frage und Antwort und hätte erhebliche Auswirkungen auf die Leistung.


5
Was ist, wenn der Generator unendlich ist?
Ende

2
Dies sollte höher sein, da dies mit einem erheblichen Zeitaufwand verbunden ist. Vielen Dank für den Hinweis.
ZdWhite

0

Das erste, was mir in den Sinn kam, war:

gen = (i for i in xrange(10))
index = 5

for i, v in zip(range(index), gen): pass

return v

0

Wenn dies zum nZeitpunkt des Authorings bekannt ist, können Sie die Destrukturierung verwenden. zB um den 3. Artikel zu bekommen:

>>> [_, _, third, *rest] = range(10)
>>> third
2
>>> rest
[3, 4, 5, 6, 7, 8, 9]

-1

Am besten zu verwenden ist: Beispiel:

a = gen values ('a','c','d','e')

Die Antwort lautet also:

a = list(a) -> this will convert the generator to a list (it will store in memory)

Wenn Sie dann einen bestimmten Index erstellen möchten, werden Sie:

a[INDEX] -> and you will able to get the value its holds 

Wenn Sie nur die Anzahl kennen oder Vorgänge ausführen möchten, die nicht im Speicher gespeichert werden müssen, gilt Folgendes: a = sum(1 in i in a)-> Hiermit wird die Anzahl der Objekte gezählt, über die Sie verfügen

Ich hoffe, ich habe es einfacher gemacht.


-2

Vielleicht sollten Sie einen tatsächlichen Anwendungsfall näher erläutern.

>>> gen = xrange(10)
>>> ind=5 
>>> gen[ind]
5

4
Ich editierte xrange(10)zu (i for i in xrange(10)). Es stellt sich heraus, dass diese Syntax funktioniert, xrangeda es sich nicht wirklich um einen Generator handelt ...
Oliver Zheng

5
xrangeälter als Generatoren und gibt ein xrange-Objekt zurück, das tatsächlich das vollständige Sequenzprotokoll implementiert.
Mike Graham
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.