Saya ingin menguraikan 2 generator dengan (berpotensi) berbeda panjang dengan zip
:
for el1, el2 in zip(gen1, gen2):
print(el1, el2)
Namun, jika gen2
memiliki lebih sedikit elemen, satu elemen tambahan gen1
adalah "dikonsumsi".
Sebagai contoh,
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
Rupanya, nilai hilang ( 8
dalam contoh saya sebelumnya) karena gen1
dibaca (sehingga menghasilkan nilai 8
) sebelum disadari gen2
tidak memiliki elemen lagi. Tetapi nilai ini menghilang di alam semesta. Ketika gen2
"lebih lama", tidak ada "masalah" seperti itu.
PERTANYAAN : Apakah ada cara untuk mengambil nilai yang hilang ini (yaitu 8
dalam contoh saya sebelumnya)? ... idealnya dengan sejumlah variabel argumen (seperti zip
halnya).
CATATAN : Saat ini saya telah menerapkan cara lain dengan menggunakan itertools.zip_longest
tetapi saya benar-benar bertanya-tanya bagaimana cara mendapatkan nilai yang hilang ini menggunakan zip
atau setara.
CATATAN 2 : Saya telah membuat beberapa pengujian dari implementasi yang berbeda dalam REPL ini jika Anda ingin mengirim dan mencoba implementasi baru :) https://repl.it/@jfthuong/MadPhysicistChester
sumber
zip()
sudah membaca8
darigen1
, itu hilang.Jawaban:
Salah satu caranya adalah dengan mengimplementasikan generator yang memungkinkan Anda melakukan cache nilai terakhir:
Untuk menggunakan ini, bungkus input ke
zip
:Penting untuk membuat
gen2
iterator daripada iterable, jadi Anda bisa tahu mana yang habis. Jikagen2
habis, Anda tidak perlu memeriksagen1.last
.Pendekatan lain adalah mengesampingkan zip untuk menerima urutan iterables yang bisa berubah dari pada iterables yang terpisah. Itu akan memungkinkan Anda untuk mengganti iterables dengan versi berantai yang menyertakan item "mengintip" Anda:
Pendekatan ini bermasalah karena berbagai alasan. Tidak hanya itu akan kehilangan iterable asli, tetapi juga akan kehilangan salah satu sifat berguna yang mungkin dimiliki oleh objek asli dengan menggantinya dengan
chain
objek.sumber
cache_last
, dan fakta bahwa itu tidak mengubahnext
perilaku ... sangat buruk itu tidak simetris (beralihgen1
dangen2
di zip akan mengarah ke hasil yang berbeda) .Cheerslast
panggilan dengan benar setelah habis. Itu akan membantu dalam mencari tahu apakah Anda membutuhkan nilai terakhir atau tidak. Juga membuatnya lebih banyak produksi-y.print(gen1.last) print(next(gen1))
isNone and 9
last
.Ini
zip
setara dengan implementasi yang diberikan dalam dokumenDalam contoh pertama Anda
gen1 = my_gen(10)
dangen2 = my_gen(8)
. Setelah kedua generator dikonsumsi hingga iterasi ke-7. Sekarang dalam 8gen1
panggilan iterasielem = next(it, sentinel)
yang mengembalikan 8 tetapi ketikagen2
panggilanelem = next(it, sentinel)
itu kembalisentinel
(karena saat inigen2
sudah habis) danif elem is sentinel
puas dan fungsi menjalankan kembali dan berhenti. Sekarangnext(gen1)
mengembalikan 9.Dalam contoh 2 Anda
gen1 = gen(8)
dangen2 = gen(10)
. Setelah kedua generator dikonsumsi hingga iterasi ke-7. Sekarang dalamgen1
panggilan iterasi ke-8elem = next(it, sentinel)
yang kembalisentinel
(karena pada titikgen1
ini habis) danif elem is sentinel
puas dan fungsi mengeksekusi kembali dan berhenti. Sekarangnext(gen2)
mengembalikan 8.Terinspirasi oleh jawaban Fisikawan Gila , Anda bisa menggunakan
Gen
pembungkus ini untuk mengatasinya:Sunting : Untuk menangani kasus yang ditunjukkan oleh Jean-Francois T.
Setelah nilai dikonsumsi dari iterator, ia akan hilang selamanya dari iterator dan tidak ada metode mutasi di tempat bagi iterator untuk menambahkannya kembali ke iterator. Salah satu penyelesaiannya adalah menyimpan nilai yang dikonsumsi terakhir.
Contoh:
sumber
gen1 = cache_last(range(0))
dangen2 = cache_last(range(2))
kemudian setelah melakukanlist(zip(gen1, gen2)
, panggilan kenext(gen2)
akan menaikkanAttributeError: 'cache_last' object has no attribute 'prev'
. # 2. Jika gen1 lebih panjang dari gen2, setelah mengkonsumsi semua elemen,next(gen2)
akan terus mengembalikan nilai terakhir sebagai gantinyaStopIteration
. Saya akan menandai jawaban MadPhysicist dan jawaban THE. Terima kasih!Saya bisa melihat Anda sudah menemukan jawaban ini dan muncul di komentar tapi saya pikir saya akan membuat jawabannya. Anda ingin menggunakan
itertools.zip_longest()
, yang akan menggantikan nilai kosong dari generator yang lebih pendek denganNone
:Cetakan:
Anda juga dapat memberikan
fillvalue
argumen saat memanggilzip_longest
untuk menggantiNone
dengan nilai default, tetapi pada dasarnya untuk solusi Anda setelah Anda menekanNone
(salah satui
atauj
) di for loop, variabel lain akan memiliki Anda8
.sumber
zip_longest
dan itu sebenarnya adalah pertanyaan saya. :)Terinspirasi oleh penjelasan @ GrandPhuba tentang
zip
, mari kita buat varian "aman" (unit-diuji di sini ):Berikut ini adalah tes dasar:
sumber
Anda bisa menggunakan itertools.tee dan itertools.islice :
sumber
Jika Anda ingin menggunakan kembali kode, solusi termudah adalah:
Anda dapat menguji kode ini menggunakan pengaturan Anda:
Itu akan mencetak:
sumber
saya tidak berpikir Anda dapat mengambil nilai yang dijatuhkan dengan dasar untuk loop, karena iterator habis, diambil dari
zip(..., ...).__iter__
yang dijatuhkan sekali habis dan Anda tidak dapat mengaksesnya.Anda harus bermutasi pos Anda, maka Anda bisa mendapatkan posisi item yang dijatuhkan dengan beberapa kode hacky)
sumber