Quiero analizar 2 generadores de (potencialmente) diferente longitud con zip
:
for el1, el2 in zip(gen1, gen2):
print(el1, el2)
Sin embargo, si gen2
tiene menos elementos, gen1
se "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 ( 8
en mi ejemplo anterior) porque gen1
se lee (generando así el valor 8
) antes de darse gen2
cuenta de que no tiene más elementos. Pero este valor desaparece en el universo. Cuando gen2
es "más largo", no existe tal "problema".
PREGUNTA : ¿Hay alguna forma de recuperar este valor faltante (es decir, 8
en mi ejemplo anterior)? ... idealmente con un número variable de argumentos (como lo zip
hace).
NOTA : Actualmente lo he implementado de otra manera usando, itertools.zip_longest
pero realmente me pregunto cómo obtener este valor faltante usando zip
o 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ído8
a 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
gen2
un iterador en lugar de un iterable, para que pueda saber cuál estaba agotado. Sigen2
está 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
chain
objeto.fuente
cache_last
y el hecho de que no altera elnext
comportamiento ... tan malo que no es simétrico (cambiargen1
ygen2
en el zip dará lugar a resultados diferentes) .Felicidadeslast
llamadas 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 9
last
.Esto es
zip
equivalente 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, lasgen1
llamadaselem = next(it, sentinel)
devuelven 8 pero cuando lasgen2
llamadaselem = next(it, sentinel)
regresansentinel
(porque en este momentogen2
están agotadas) yif elem is sentinel
está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 puntogen1
está agotado) yif elem is sentinel
se satisface y la función ejecuta return y se detiene. Ahoranext(gen2)
devuelve 8.Inspirado por la respuesta de Mad Physicist , puedes usar este
Gen
envoltorio 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
fillvalue
argumento al llamarzip_longest
para reemplazar elNone
con un valor predeterminado, pero básicamente para su solución una vez que presione unNone
(oi
oj
) en el bucle for, la otra variable tendrá su8
.fuente
zip_longest
y realmente estaba en mi pregunta. :)Inspirado por la aclaración de @ GrandPhuba, creemos
zip
una 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