Chcę przeanalizować 2 generatory (potencjalnie) różnej długości za pomocą zip
:
for el1, el2 in zip(gen1, gen2):
print(el1, el2)
Jeśli jednak gen2
ma mniej elementów, jeden dodatkowy element gen1
jest „konsumowany”.
Na przykład,
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
Najwyraźniej brakuje wartości ( 8
w moim poprzednim przykładzie), ponieważ gen1
jest ona odczytywana (generując wartość 8
), zanim się zorientuje, gen2
że nie ma już żadnych elementów. Ale ta wartość znika we wszechświecie. Kiedy gen2
jest „dłuższy”, nie ma takiego „problemu”.
PYTANIE : Czy istnieje sposób na odzyskanie tej brakującej wartości (tj. 8
W moim poprzednim przykładzie)? ... najlepiej ze zmienną liczbą argumentów (podobnie jak zip
robi).
UWAGA : Obecnie zaimplementowałem w inny sposób, używając, itertools.zip_longest
ale naprawdę zastanawiam się, jak uzyskać tę brakującą wartość przy użyciu zip
lub równoważnej.
UWAGA 2 : Stworzyłem niektóre testy różnych implementacji w niniejszej REPL na wypadek, gdybyś chciał przesłać i wypróbować nową implementację :) https://repl.it/@jfthuong/MadPhysicistChester
źródło
zip()
zapoznał się8
zgen1
, to nie ma.Odpowiedzi:
Jednym ze sposobów byłoby zaimplementowanie generatora, który pozwala buforować ostatnią wartość:
Aby tego użyć, zawiń dane wejściowe do
zip
:Ważne jest, aby utworzyć
gen2
iterator zamiast iterowalnego, abyś mógł wiedzieć, który z nich został wyczerpany. Jeśligen2
jest wyczerpany, nie musisz sprawdzaćgen1.last
.Innym podejściem byłoby zastąpienie zip, aby zaakceptować zmienną sekwencję iteracji zamiast osobnych iteracji. To pozwoli ci zamienić iterowalne na łańcuchową wersję, która zawiera twój „zerknięty” element:
Takie podejście jest problematyczne z wielu powodów. Utraci nie tylko pierwotną iterowalność, ale także utraci wszelkie użyteczne właściwości, które pierwotny obiekt mógł mieć, zastępując go
chain
przedmiotem.źródło
cache_last
i fakt, że nie zmienia tonext
zachowania ... tak źle, że nie jest symetryczne (zmianagen1
igen2
w zipie prowadzi do różnych wyników). Cheerslast
połączenia po jego wyczerpaniu. To powinno pomóc w ustaleniu, czy potrzebujesz ostatniej wartości, czy nie. Sprawia również, że jest bardziej produktywny.print(gen1.last) print(next(gen1))
toNone and 9
last
.Jest to
zip
odpowiednik implementacji podany w dokumentachW twoim pierwszym przykładzie
gen1 = my_gen(10)
igen2 = my_gen(8)
. Po zużyciu obu generatorów do siódmej iteracji. Teraz w 8. iteracjigen1
wywołania,elem = next(it, sentinel)
które zwracają 8, ale gdygen2
wywołaniaelem = next(it, sentinel)
, zwracająsentinel
(ponieważ w tym momenciegen2
są wyczerpane) iif elem is sentinel
są spełnione, a funkcja wykonuje return i zatrzymuje. Teraznext(gen1)
zwraca 9.W twoim drugim przykładzie
gen1 = gen(8)
igen2 = gen(10)
. Po zużyciu obu generatorów do siódmej iteracji. Teraz w ósmej iteracjigen1
wywołuje,elem = next(it, sentinel)
która zwracasentinel
(ponieważ w tym momenciegen1
jest wyczerpana) iif elem is sentinel
jest spełniona, a funkcja wykonuje return i zatrzymuje się. Teraznext(gen2)
zwraca 8.Zainspirowany odpowiedzią Szalonego Fizyka możesz użyć tego
Gen
opakowania, aby temu przeciwdziałać:Edycja : Aby obsłużyć sprawy wskazane przez Jean-Francois T.
Gdy wartość zostanie zużyta z iteratora, na zawsze zniknie z iteratora i nie ma żadnej metody mutacji w miejscu dla iteratorów, aby dodać ją z powrotem do iteratora. Jednym z obejść jest przechowywanie ostatnio wykorzystanej wartości.
Przykłady:
źródło
gen1 = cache_last(range(0))
agen2 = cache_last(range(2))
następnie po wykonaniulist(zip(gen1, gen2)
, wezwanie donext(gen2)
podniesieAttributeError: 'cache_last' object has no attribute 'prev'
. # 2. Jeśli gen1 jest dłuższy niż gen2, po zużyciu wszystkich elementównext(gen2)
będzie nadal zwracał ostatnią wartość zamiastStopIteration
. Oznaczę odpowiedź MadPhysicist i THE. Dzięki!Widzę, że już znalazłeś tę odpowiedź i została ona poruszona w komentarzach, ale pomyślałem, że dam odpowiedź. Chcesz użyć
itertools.zip_longest()
, który zastąpi puste wartości krótszego generatoraNone
:Wydruki:
Możesz również podać
fillvalue
argument podczas wywoływania wzip_longest
celu zastąpieniaNone
wartości domyślnej, ale w zasadzie dla twojego rozwiązania, gdy trafiszNone
(wi
lubj
w pętli for), inna zmienna będzie miała twoją8
.źródło
zip_longest
i tak właśnie było w moim pytaniu. :)Zainspirowani wyjaśnieniem @ GrandPhuba
zip
, stwórzmy „bezpieczny” wariant (testowany tutaj tutaj ):Oto podstawowy test:
źródło
możesz użyć itertools.tee i itertools.islice :
źródło
Jeśli chcesz ponownie użyć kodu, najłatwiejszym rozwiązaniem jest:
Możesz przetestować ten kod, używając swojej konfiguracji:
Wydrukuje:
źródło
nie sądzę, że można odzyskać upuszczoną wartość za pomocą podstawowej pętli for, ponieważ wyczerpany iterator jest pobierany po
zip(..., ...).__iter__
upuszczeniu po wyczerpaniu i nie można uzyskać do niego dostępu.Powinieneś mutować zip, a następnie możesz uzyskać pozycję upuszczonego przedmiotu z jakimś hackym kodem)
źródło