Ich möchte 2 Generatoren (möglicherweise) unterschiedlicher Länge analysieren mit zip
:
for el1, el2 in zip(gen1, gen2):
print(el1, el2)
Wenn gen2
jedoch weniger Elemente vorhanden sind, wird ein zusätzliches Element von gen1
"verbraucht".
Zum Beispiel,
def my_gen(n:int):
for i in range(n):
yield i
gen1 = my_gen(10)
gen2 = my_gen(8)
list(zip(gen1, gen2)) # Last tuple is (7, 7)
print(next(gen1)) # printed value is "9" => 8 is missing
gen1 = my_gen(8)
gen2 = my_gen(10)
list(zip(gen1, gen2)) # Last tuple is (7, 7)
print(next(gen2)) # printed value is "8" => OK
Anscheinend fehlt ein Wert ( 8
in meinem vorherigen Beispiel), weil er gen1
gelesen wird (wodurch der Wert generiert wird 8
), bevor er erkennt, dass er gen2
keine weiteren Elemente enthält. Aber dieser Wert verschwindet im Universum. Wenn gen2
"länger" ist, gibt es kein solches "Problem".
FRAGE : Gibt es eine Möglichkeit, diesen fehlenden Wert abzurufen (dh 8
in meinem vorherigen Beispiel)? ... idealerweise mit einer variablen Anzahl von Argumenten (wie es der zip
Fall ist).
HINWEIS : Ich habe derzeit eine andere Implementierung mithilfe von verwendet, itertools.zip_longest
aber ich frage mich wirklich, wie ich diesen fehlenden Wert mithilfe von zip
oder einem gleichwertigen Wert erhalten kann .
HINWEIS 2 : Ich habe einige Tests der verschiedenen Implementierungen in dieser REPL erstellt, falls Sie eine neue Implementierung einreichen und ausprobieren möchten :) https://repl.it/@jfthuong/MadPhysicistChester
quelle
zip()
gelesen hat ,8
ausgen1
, es ist weg.Antworten:
Eine Möglichkeit wäre, einen Generator zu implementieren, mit dem Sie den letzten Wert zwischenspeichern können:
Um dies zu verwenden, wickeln Sie die Eingaben in
zip
:Es ist wichtig,
gen2
einen Iterator anstelle eines iterierbaren zu erstellen, damit Sie wissen, welcher erschöpft war. Wenn Siegen2
erschöpft sind, müssen Sie nicht überprüfengen1.last
.Ein anderer Ansatz wäre, zip zu überschreiben, um eine veränderbare Folge von Iterables anstelle von separaten Iterables zu akzeptieren. Auf diese Weise können Sie iterables durch eine verkettete Version ersetzen, die Ihr "Peeked" -Element enthält:
Dieser Ansatz ist aus vielen Gründen problematisch. Es verliert nicht nur die ursprüngliche Iterierbarkeit, sondern auch alle nützlichen Eigenschaften des ursprünglichen Objekts, wenn es durch ein
chain
Objekt ersetzt wird.quelle
cache_last
und die Tatsache, dass es dasnext
Verhalten nicht verändert ... so schlecht, dass es nicht symmetrisch ist (das Umschaltengen1
undgen2
im Reißverschluss führt zu unterschiedlichen Ergebnissen) .Cheerslast
Anrufe richtig zu reagieren, nachdem er erschöpft ist. Das sollte helfen, herauszufinden, ob Sie den letzten Wert benötigen oder nicht. Macht es auch produktionsreicher.print(gen1.last) print(next(gen1))
istNone and 9
last
.Dies ist
zip
das in den Dokumenten angegebene ImplementierungsäquivalentIn Ihrem 1. Beispiel
gen1 = my_gen(10)
undgen2 = my_gen(8)
. Nachdem beide Generatoren bis zur 7. Iteration verbraucht sind. Jetzt in der 8. Iteration werdengen1
Aufrufe zurückgegeben,elem = next(it, sentinel)
die 8 zurückgeben, aber wenngen2
Aufrufeelem = next(it, sentinel)
zurückgegeben werdensentinel
(weil diesgen2
erschöpft ist) undif elem is sentinel
erfüllt sind und die Funktion return ausführt und stoppt. Gibt jetztnext(gen1)
9 zurück.In Ihrem 2. Beispiel
gen1 = gen(8)
undgen2 = gen(10)
. Nachdem beide Generatoren bis zur 7. Iteration verbraucht sind. Jetzt in der 8. Iterationgen1
Aufrufe,elem = next(it, sentinel)
die zurückkehrensentinel
(weil zu diesem Zeitpunktgen1
erschöpft ist) undif elem is sentinel
erfüllt sind und die Funktion return ausführt und stoppt. Gibt jetztnext(gen2)
8 zurück.Inspiriert von der Antwort von Mad Physicist , könnten Sie diesen
Gen
Wrapper verwenden, um dem entgegenzuwirken:Bearbeiten : Um die Fälle von Jean-Francois T. zu behandeln.
Sobald ein Wert vom Iterator verbraucht ist, ist er vom Iterator für immer verschwunden, und es gibt keine direkte Mutationsmethode für Iteratoren, um ihn wieder zum Iterator hinzuzufügen. Eine Lösung besteht darin, den zuletzt verbrauchten Wert zu speichern.
Beispiele:
quelle
gen1 = cache_last(range(0))
undgen2 = cache_last(range(2))
dann nach,list(zip(gen1, gen2)
wird ein Anruf annext(gen2)
eine auslösenAttributeError: 'cache_last' object has no attribute 'prev'
. # 2. Wenn gen1 länger als gen2 ist, wird nach dem Verzehr aller Elementenext(gen2)
weiterhin der letzte Wert anstelle von zurückgegebenStopIteration
. Ich werde MadPhysicist Antwort und DIE Antwort markieren. Vielen Dank!Ich kann sehen, dass Sie diese Antwort bereits gefunden haben und sie in den Kommentaren erwähnt wurde, aber ich dachte, ich werde eine Antwort daraus machen. Sie möchten verwenden
itertools.zip_longest()
, wodurch die leeren Werte des kürzeren Generators ersetzt werden durchNone
:Drucke:
Sie können auch ein
fillvalue
Argument angebenzip_longest
, wenn Sie aufrufen , um dasNone
durch einen Standardwert zu ersetzen. Grundsätzlich gilt jedoch für Ihre Lösung, sobald Sie einNone
(entwederi
oderj
) in der for-Schleife drücken, die andere Variable mit Ihrem8
.quelle
zip_longest
und es war eigentlich in meiner Frage. :)Inspiriert von @ GrandPhuba der Aufklärung
zip
, lassen Sie sich eine „sichere“ Variante an (Einheit geprüft hier ):Hier ist ein grundlegender Test:
quelle
Sie könnten itertools.tee und itertools.islice verwenden :
quelle
Wenn Sie Code wiederverwenden möchten, ist die einfachste Lösung:
Sie können diesen Code mit Ihrem Setup testen:
Es wird gedruckt:
quelle
Ich glaube nicht, dass Sie mit der Basis-for-Schleife einen abgelegten Wert abrufen können, da der Iterator
zip(..., ...).__iter__
erschöpft ist und nicht mehr gelöscht werden kann, wenn er erschöpft ist.Sie sollten Ihren Reißverschluss mutieren, dann können Sie die Position des abgelegten Gegenstands mit einem Hacky-Code ermitteln.
quelle