Je veux analyser 2 générateurs de longueur (potentiellement) différente avec zip
:
for el1, el2 in zip(gen1, gen2):
print(el1, el2)
Cependant, s'il 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, une valeur manque ( 8
dans mon exemple précédent) car elle gen1
est lue (générant ainsi la valeur 8
) avant de se rendre compte qu'elle gen2
n'a plus d'éléments. Mais cette valeur disparaît dans l'univers. Quand gen2
est "plus long", il n'y a pas un tel "problème".
QUESTION : Existe-t-il un moyen de récupérer cette valeur manquante (c'est- 8
à- dire dans mon exemple précédent)? ... idéalement avec un nombre variable d'arguments (comme le zip
fait).
REMARQUE : 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
la source
zip()
a lu à8
partirgen1
, il est parti.Réponses:
Une façon serait d'implémenter un générateur qui vous permet de mettre en cache la dernière valeur:
Pour l'utiliser, encapsulez les entrées pour
zip
:Il est important de créer
gen2
un itérateur plutôt qu'un itérable, afin que vous puissiez savoir lequel a été épuisé. Sigen2
est épuisé, vous n'avez pas besoin de vérifiergen1.last
.Une autre approche consisterait à remplacer zip pour accepter une séquence mutable d'itérables au lieu d'itérables séparés. Cela vous permettrait de remplacer les itérables par une version chaînée qui inclut votre élément "jeté un œil":
Cette approche est problématique pour de nombreuses raisons. Non seulement il perdra l'itérable d'origine, mais il perdra toutes les propriétés utiles que l'objet d'origine aurait pu avoir en le remplaçant par un
chain
objet.la source
cache_last
, et le fait qu'elle ne modifie pas lenext
comportement ... tant pis ce n'est pas symétrique (la commutationgen1
etgen2
dans le zip conduira à des résultats différents) Cheerslast
appels une fois qu'il est épuisé. Cela devrait aider à déterminer si vous avez besoin de la dernière valeur ou non. Le rend également plus productif.print(gen1.last) print(next(gen1))
isNone and 9
last
.C'est l'
zip
équivalent d'implémentation donné dans la documentationDans votre 1er exemple
gen1 = my_gen(10)
etgen2 = my_gen(8)
. Après les deux générateurs sont consommés jusqu'à la 7ème itération. Maintenant, à la 8e itération, lesgen1
appelselem = next(it, sentinel)
renvoient 8, mais lorsque lesgen2
appelselem = next(it, sentinel)
reviennentsentinel
(car à ce moment, ilsgen2
sont épuisés) etif elem is sentinel
sont satisfaits et la fonction exécute le retour et s'arrête. Renvoie maintenantnext(gen1)
9.Dans votre 2ème exemple
gen1 = gen(8)
etgen2 = gen(10)
. Après les deux générateurs sont consommés jusqu'à la 7ème itération. Maintenant, à la 8e itération, l'gen1
appelelem = next(it, sentinel)
revientsentinel
(car à ce stadegen1
est épuisé) etif elem is sentinel
est satisfait et la fonction exécute le retour et s'arrête. Renvoie maintenantnext(gen2)
8.Inspiré par la réponse de Mad Physicist , vous pouvez utiliser ce
Gen
wrapper pour le contrer:Edit : Pour gérer les cas pointés par Jean-François T.
Une fois qu'une valeur est consommée par l'itérateur, elle disparaît à jamais de l'itérateur et il n'y a pas de méthode de mutation sur place pour que les itérateurs la rajoutent à l'itérateur. Une solution consiste à stocker la dernière valeur consommée.
Exemples:
la source
gen1 = cache_last(range(0))
etgen2 = cache_last(range(2))
ensuite après avoir faitlist(zip(gen1, gen2)
, un appel ànext(gen2)
fera monter unAttributeError: 'cache_last' object has no attribute 'prev'
. # 2. Si gen1 est plus long que gen2, après avoir consommé tous les éléments,next(gen2)
continuera de renvoyer la dernière valeur au lieu deStopIteration
. Je marquerai la réponse de MadPhysicist et LA réponse. Merci!Je peux voir que vous avez déjà trouvé cette réponse et elle a été évoquée dans les commentaires, mais j'ai pensé que j'en ferais une réponse. Vous souhaitez utiliser
itertools.zip_longest()
, qui remplacera les valeurs vides du générateur le plus court parNone
:Tirages:
Vous pouvez également fournir un
fillvalue
argument lors de l'appelzip_longest
pour remplacer leNone
par une valeur par défaut, mais fondamentalement pour votre solution une fois que vous avez frappé unNone
(oui
ouj
) dans la boucle for, l'autre variable aura votre8
.la source
zip_longest
et c'était en fait ma question. :)Inspiré par l'élucidation de @ GrandPhuba
zip
, créons une variante "sûre" (testée ici ):Voici un test de base:
la source
vous pouvez utiliser itertools.tee et itertools.islice :
la source
Si vous souhaitez réutiliser du code, la solution la plus simple est:
Vous pouvez tester ce code à l'aide de votre configuration:
Il imprimera:
la source
Je ne pense pas que vous puissiez récupérer la valeur perdue avec la boucle for de base, car l'itérateur épuisé, tiré de
zip(..., ...).__iter__
suppression une fois épuisé et vous ne pouvez pas y accéder.Vous devez muter votre zip, puis vous pouvez obtenir la position de l'élément déposé avec du code hacky)
la source