Quero analisar 2 geradores de comprimento (potencialmente) diferente com zip
:
for el1, el2 in zip(gen1, gen2):
print(el1, el2)
No entanto, se gen2
tiver menos elementos, um elemento extra de gen1
é "consumido".
Por exemplo,
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, um valor está ausente ( 8
no meu exemplo anterior) porque gen1
é lido (gerando o valor 8
) antes que ele perceba gen2
que não possui mais elementos. Mas esse valor desaparece no universo. Quando gen2
é "mais longo", não existe esse "problema".
PERGUNTA : Existe uma maneira de recuperar esse valor ausente (ou seja, 8
no meu exemplo anterior)? ... idealmente com um número variável de argumentos (como zip
faz).
NOTA : No momento, eu implementei de outra maneira usando, itertools.zip_longest
mas realmente me pergunto como obter esse valor ausente usando zip
ou equivalente.
NOTA 2 : Criei alguns testes das diferentes implementações neste REPL, caso você queira enviar e tentar uma nova implementação :) https://repl.it/@jfthuong/MadPhysicistChester
fonte
zip()
tenha lido8
a partirgen1
, ela se foi.Respostas:
Uma maneira seria implementar um gerador que permita armazenar em cache o último valor:
Para usar isso, enrole as entradas para
zip
:É importante criar
gen2
um iterador em vez de iterável, para que você possa saber qual deles estava esgotado. Segen2
estiver esgotado, você não precisa verificargen1.last
.Outra abordagem seria substituir o zip para aceitar uma sequência mutável de iteráveis em vez de iteráveis separados. Isso permitiria substituir iterables por uma versão encadeada que inclui o item "espiado":
Essa abordagem é problemática por vários motivos. Não apenas perderá o iterável original, mas também perderá qualquer uma das propriedades úteis que o objeto original pode ter ao substituí-lo por um
chain
objeto.fonte
cache_last
, eo fato de que ele não altera onext
comportamento ... tão ruim que não é simétrica (comutaçãogen1
egen2
no Zip conduz a resultados diferentes) .Cheerslast
chamadas após o esgotamento. Isso deve ajudar a descobrir se você precisa do último valor ou não. Também o torna mais produtivo-y.print(gen1.last) print(next(gen1))
isNone and 9
last
.Isso é
zip
equivalente à implementação, fornecido nos documentosNo seu primeiro exemplo
gen1 = my_gen(10)
egen2 = my_gen(8)
. Depois que os dois geradores são consumidos até a 7ª iteração. Agora, na 8a iteração, asgen1
chamadaselem = next(it, sentinel)
retornam 8, mas quando asgen2
chamadaselem = next(it, sentinel)
retornamsentinel
(porque issogen2
está esgotado) eif elem is sentinel
são satisfeitas e a função executa o retorno e as paradas. Agoranext(gen1)
retorna 9.No seu segundo exemplo
gen1 = gen(8)
egen2 = gen(10)
. Depois que os dois geradores são consumidos até a 7ª iteração. Agora, na oitava iteração,gen1
chama oelem = next(it, sentinel)
retornosentinel
(porque neste momentogen1
está esgotado) eif elem is sentinel
é satisfeito e a função executa o retorno e para. Agoranext(gen2)
retorna 8.Inspirado na resposta do Mad Physicist , você pode usar este
Gen
invólucro para combatê-lo:Edit : Para lidar com os casos apontados por Jean-Francois T.
Depois que um valor é consumido do iterador, ele se torna para sempre do iterador e não há um método de mutação no local para que os iteradores o adicionem novamente ao iterador. Uma solução alternativa é armazenar o último valor consumido.
Exemplos:
fonte
gen1 = cache_last(range(0))
egen2 = cache_last(range(2))
depois de fazerlist(zip(gen1, gen2)
, uma chamada paranext(gen2)
aumentará umAttributeError: 'cache_last' object has no attribute 'prev'
. # 2 Se gen1 for maior que gen2, depois de consumir todos os elementos,next(gen2)
continuará retornando o último valor em vez deStopIteration
. Marcarei a resposta MadPhysicist e A resposta. Obrigado!Eu posso ver que você já encontrou essa resposta e ela foi mencionada nos comentários, mas achei que seria uma resposta. Você deseja usar
itertools.zip_longest()
, que substituirá os valores vazios do gerador mais curto porNone
:Impressões:
Você também pode fornecer um
fillvalue
argumento ao chamarzip_longest
para substituir oNone
por um valor padrão, mas basicamente para a sua solução depois de pressionar umNone
(oui
ouj
) no loop for, a outra variável terá o seu8
.fonte
zip_longest
e estava na minha pergunta, na verdade. :)Inspirado na elucidação de @ GrandPhuba
zip
, vamos criar uma variante "segura" (testada aqui ):Aqui está um teste básico:
fonte
você poderia usar itertools.tee e itertools.islice :
fonte
Se você deseja reutilizar o código, a solução mais fácil é:
Você pode testar esse código usando sua configuração:
Irá imprimir:
fonte
eu não acho que você pode recuperar o valor descartado com o loop for básico, porque o iterador esgotado, retirado de
zip(..., ...).__iter__
ser descartado uma vez esgotado, e você não pode acessá-lo.Você deve alterar seu zip, para obter a posição do item descartado com algum código hacky)
fonte