Quiero analizar 2 generadores de (potencialmente) diferente longitud con zip:
for el1, el2 in zip(gen1, gen2):
print(el1, el2)
Sin embargo, si gen2tiene menos elementos, gen1se "consume" un elemento adicional .
Por ejemplo,
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
Aparentemente, falta un valor ( 8en mi ejemplo anterior) porque gen1se lee (generando así el valor 8) antes de darse gen2cuenta de que no tiene más elementos. Pero este valor desaparece en el universo. Cuando gen2es "más largo", no existe tal "problema".
PREGUNTA : ¿Hay alguna forma de recuperar este valor faltante (es decir, 8en mi ejemplo anterior)? ... idealmente con un número variable de argumentos (como lo ziphace).
NOTA : Actualmente lo he implementado de otra manera usando, itertools.zip_longestpero realmente me pregunto cómo obtener este valor faltante usando zipo equivalente.
NOTA 2 : He creado algunas pruebas de las diferentes implementaciones en este REPL en caso de que desee enviar y probar una nueva implementación :) https://repl.it/@jfthuong/MadPhysicistChester
fuente

zip()haya leído8a partirgen1, se ha ido.Respuestas:
Una forma sería implementar un generador que le permita almacenar en caché el último valor:
Para usar esto, ajuste las entradas a
zip:Es importante hacer
gen2un iterador en lugar de un iterable, para que pueda saber cuál estaba agotado. Sigen2está agotado, no necesita verificargen1.last.Otro enfoque sería anular zip para aceptar una secuencia mutable de iterables en lugar de iterables separados. Eso le permitiría reemplazar iterables con una versión encadenada que incluye su elemento "echado un vistazo":
Este enfoque es problemático por muchas razones. No solo perderá el iterable original, sino que perderá cualquiera de las propiedades útiles que pueda haber tenido el objeto original al reemplazarlo con un
chainobjeto.fuente
cache_lasty el hecho de que no altera elnextcomportamiento ... tan malo que no es simétrico (cambiargen1ygen2en el zip dará lugar a resultados diferentes) .Felicidadeslastllamadas después de que se haya agotado. Eso debería ayudar a determinar si necesita el último valor o no. También lo hace más productivo.print(gen1.last) print(next(gen1))esNone and 9last.Esto es
zipequivalente a la implementación dada en documentosEn su primer ejemplo
gen1 = my_gen(10)ygen2 = my_gen(8). Después de que ambos generadores se consumen hasta la 7ª iteración. Ahora, en la octava iteración, lasgen1llamadaselem = next(it, sentinel)devuelven 8 pero cuando lasgen2llamadaselem = next(it, sentinel)regresansentinel(porque en este momentogen2están agotadas) yif elem is sentinelestán satisfechas y la función ejecuta return y se detiene. Ahoranext(gen1)devuelve 9.En su segundo ejemplo
gen1 = gen(8)ygen2 = gen(10). Después de que ambos generadores se consuman hasta la 7ª iteración. Ahora, en la octava iteracióngen1, seelem = next(it, sentinel)devuelvesentinel(porque en este puntogen1está agotado) yif elem is sentinelse satisface y la función ejecuta return y se detiene. Ahoranext(gen2)devuelve 8.Inspirado por la respuesta de Mad Physicist , puedes usar este
Genenvoltorio para contrarrestarlo:Editar : Para manejar los casos señalados por Jean-Francois T.
Una vez que el iterador consume un valor, este desaparece para siempre y no hay un método de mutación en el lugar para que los iteradores lo agreguen nuevamente al iterador. Una solución es almacenar el último valor consumido.
Ejemplos:
fuente
gen1 = cache_last(range(0))ygen2 = cache_last(range(2))luego de hacerlolist(zip(gen1, gen2), una llamada anext(gen2)levantará unAttributeError: 'cache_last' object has no attribute 'prev'. # 2 Si gen1 es más largo que gen2, después de consumir todos los elementos,next(gen2)seguirá devolviendo el último valor en lugar deStopIteration. Marcaré la respuesta MadPhysicist y LA respuesta. ¡Gracias!Puedo ver que ya has encontrado esta respuesta y apareció en los comentarios, pero pensé que la respondería. Desea usar
itertools.zip_longest(), que reemplazará los valores vacíos del generador más corto conNone:Huellas dactilares:
También puede proporcionar un
fillvalueargumento al llamarzip_longestpara reemplazar elNonecon un valor predeterminado, pero básicamente para su solución una vez que presione unNone(oioj) en el bucle for, la otra variable tendrá su8.fuente
zip_longesty realmente estaba en mi pregunta. :)Inspirado por la aclaración de @ GrandPhuba, creemos
zipuna variante "segura" (probada por unidad aquí ):Aquí hay una prueba básica:
fuente
podría usar itertools.tee e itertools.islice :
fuente
Si desea reutilizar el código, la solución más fácil es:
Puede probar este código usando su configuración:
Imprimirá:
fuente
no creo que pueda recuperar el valor eliminado con el bucle básico for, porque el iterador agotado, tomado de
zip(..., ...).__iter__ser eliminado una vez agotado y no puede acceder a él.Debe mutar su código postal, luego puede obtener la posición del artículo que se cayó con algún código hacky)
fuente