web-dev-qa-db-fra.com

Zipped Python avec le 2ème étant plus court: comment récupérer un élément consommé en silence

Je veux analyser 2 générateurs de longueur (potentiellement) différente avec Zip:

for el1, el2 in Zip(gen1, gen2):
    print(el1, el2)

Toutefois, si gen2 a moins d'éléments, un élément supplémentaire de gen1 est "consommé".

Par exemple,

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

Apparemment, il manque une valeur (8 dans mon exemple précédent) parce que gen1 est lu (générant ainsi la valeur 8) avant de réaliser gen2 n'a plus d'éléments. Mais cette valeur disparaît dans l'univers. Quand gen2 est "plus", il n'y a pas de "problème".

[~ # ~] question [~ # ~] : existe-t-il un moyen de récupérer cette valeur manquante (c'est-à-dire 8 dans mon exemple précédent)? ... idéalement avec un nombre variable d'arguments (comme Zip le fait).

[~ # ~] note [~ # ~]: J'ai actuellement implémenté d'une autre manière en utilisant itertools.Zip_longest mais je me demande vraiment comment obtenir cette valeur manquante en utilisant Zip ou équivalent.

NOTE 2: J'ai créé quelques tests des différentes implémentations dans ce REPL au cas où vous voudriez soumettre et essayer une nouvelle implémentation :) https://repl.it/@jfthuong/MadPhysicistChester

54
Jean-Francois T.

Si vous souhaitez réutiliser du code, la solution la plus simple est:

from more_itertools import peekable

a = peekable(a)
b = peekable(b)

while True:
    try:
        a.peek()
        b.peek()
    except StopIteration:
        break
    x = next(a)
    y = next(b)
    print(x, y)


print(list(a), list(b))  # Misses nothing.

Vous pouvez tester ce code en utilisant votre configuration:

def my_gen(n: int):
    yield from range(n)

a = my_gen(10)
b = my_gen(8)

Il imprimera:

0 0
1 1
2 2
3 3
4 4
5 5
6 6
7 7
[8, 9] []
3
Neil G

je ne pense pas que vous puissiez récupérer la valeur supprimée avec la boucle for de base, car l'itérateur épuisé, extrait de Zip(..., ...).__iter__, est supprimé une fois épuisé et vous ne pouvez pas y accéder.

Vous devez faire muter votre Zip, vous pouvez alors obtenir la position de l'élément déposé avec du code hacky)

z = Zip(range(10), range(8))
for _ in iter(z.__next__, None):
    ...
_, (one, other) = z.__reduce__()
_, (i_one,), p_one = one.__reduce__() # p_one == current pos, 1 based
import itertools
val = next(itertools.islice(iter(i_one), p_one - 1, p_one))