Bagaimana cara zip (* [iter (s)] * n) bekerja dengan Python?

103
s = [1,2,3,4,5,6,7,8,9]
n = 3

zip(*[iter(s)]*n) # returns [(1,2,3),(4,5,6),(7,8,9)]

Bagaimana cara zip(*[iter(s)]*n)kerjanya? Seperti apa jadinya jika ditulis dengan lebih banyak kode verbose?

Oliver Zheng
sumber
1
juga lihat di sini di mana cara kerjanya juga dijelaskan: stackoverflow.com/questions/2202461/…
Matt Joiner
jika jawaban di sini tidak cukup, saya menuliskannya di blog: telliott99.blogspot.com/2010/01/…
telliott99
7
Meskipun sangat menarik, teknik ini harus bertentangan dengan nilai inti "keterbacaan" dari Python!
Demis

Jawaban:

108

iter()adalah iterator yang berurutan. [x] * nmenghasilkan daftar yang berisi nkuantitas x, yaitu daftar panjang n, di mana setiap elemen berada x. *argmembongkar urutan menjadi argumen untuk panggilan fungsi. Oleh karena itu, Anda meneruskan iterator yang sama 3 kali ke zip(), dan itu menarik item dari iterator setiap saat.

x = iter([1,2,3,4,5,6,7,8,9])
print zip(x, x, x)
Ignacio Vazquez-Abrams
sumber
1
Perlu diketahui: ketika iterator yields (= returns) item, Anda dapat membayangkan item ini sebagai "dikonsumsi". Jadi pada saat iterator dipanggil, itu menghasilkan item "tidak dikonsumsi" berikutnya.
winklerrr
46

Jawaban dan komentar hebat lainnya menjelaskan dengan baik peran argument unpacking dan zip () .

Seperti yang dikatakan Ignacio dan ujukatzel , Anda meneruskan ke zip()tiga referensi ke iterator yang sama dan zip()membuat 3-tupel dari bilangan bulat — secara berurutan — dari setiap referensi ke iterator:

1,2,3,4,5,6,7,8,9  1,2,3,4,5,6,7,8,9  1,2,3,4,5,6,7,8,9
^                    ^                    ^            
      ^                    ^                    ^
            ^                    ^                    ^

Dan karena Anda meminta contoh kode yang lebih panjang:

chunk_size = 3
L = [1,2,3,4,5,6,7,8,9]

# iterate over L in steps of 3
for start in range(0,len(L),chunk_size): # xrange() in 2.x; range() in 3.x
    end = start + chunk_size
    print L[start:end] # three-item chunks

Mengikuti nilai startdan end:

[0:3) #[1,2,3]
[3:6) #[4,5,6]
[6:9) #[7,8,9]

FWIW, Anda bisa mendapatkan hasil yang sama dengan map()argumen awal None:

>>> map(None,*[iter(s)]*3)
[(1, 2, 3), (4, 5, 6), (7, 8, 9)]

Untuk lebih lanjut tentang zip()dan map(): http://muffinresearch.co.uk/archives/2007/10/16/python-transposing-lists-with-map-and-zip/

mechanical_meat
sumber
31

Saya pikir satu hal yang terlewat di semua jawaban (mungkin jelas bagi mereka yang akrab dengan iterator) tetapi tidak begitu jelas bagi orang lain adalah -

Karena kita memiliki iterator yang sama, itu dikonsumsi dan elemen yang tersisa digunakan oleh zip. Jadi jika kita hanya menggunakan daftar dan bukan iter misalnya.

l = range(9)
zip(*([l]*3)) # note: not an iter here, the lists are not emptied as we iterate 
# output 
[(0, 0, 0), (1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4), (5, 5, 5), (6, 6, 6), (7, 7, 7), (8, 8, 8)]

Dengan menggunakan iterator, munculkan nilai dan hanya tetap tersedia, jadi untuk zip setelah 0 dikonsumsi 1 tersedia dan kemudian 2 dan seterusnya. Hal yang sangat halus, tapi cukup pintar !!!

gabhijit
sumber
+1, Anda menyelamatkan saya! Saya tidak percaya bahwa jawaban lain melewatkan detail penting ini dengan asumsi semua orang tahu ini. Dapatkah Anda memberikan referensi ke dokumentasi yang menyertakan informasi ini?
Snehasish Karmakar
9

iter(s) mengembalikan iterator untuk s.

[iter(s)]*n membuat daftar n kali iterator yang sama untuk s.

Jadi, saat melakukannya zip(*[iter(s)]*n), ia mengekstrak item dari ketiga iterator dari daftar secara berurutan. Karena semua iterator adalah objek yang sama, itu hanya mengelompokkan daftar dalam potongan n.

sttwister
sumber
7
Bukan 'n iterator dari daftar yang sama', tetapi 'n kali objek iterator yang sama'. Objek iterator yang berbeda tidak berbagi status, meskipun mereka berada dalam daftar yang sama.
Thomas Wouters
Terima kasih, dikoreksi. Memang itulah yang saya "pikirkan", tetapi menulis sesuatu yang lain.
sttwister
6

Satu kata nasihat untuk menggunakan zip dengan cara ini. Ini akan memotong daftar Anda jika panjangnya tidak dapat dibagi rata. Untuk mengatasi ini, Anda dapat menggunakan itertools.izip_longest jika Anda dapat menerima nilai isian. Atau Anda bisa menggunakan sesuatu seperti ini:

def n_split(iterable, n):
    num_extra = len(iterable) % n
    zipped = zip(*[iter(iterable)] * n)
    return zipped if not num_extra else zipped + [iterable[-num_extra:], ]

Pemakaian:

for ints in n_split(range(1,12), 3):
    print ', '.join([str(i) for i in ints])

Cetakan:

1, 2, 3
4, 5, 6
7, 8, 9
10, 11
jmagnusson.dll
sumber
3
Ini sudah didokumentasikan di itertoolsresep: docs.python.org/2/library/itertools.html#recipes grouper . Tidak perlu menemukan kembali roda
jamylak
1

Mungkin lebih mudah untuk melihat apa yang terjadi dengan interpreter python atau ipythondengan n = 2:

In [35]: [iter("ABCDEFGH")]*2
Out[35]: [<iterator at 0x6be4128>, <iterator at 0x6be4128>]

Jadi, kami memiliki daftar dua iterator yang menunjuk ke objek iterator yang sama. Ingatlah bahwa iterpada suatu objek mengembalikan objek iterator dan dalam skenario ini, itu adalah iterator yang sama dua kali karena *2gula sintaksis python. Iterator juga hanya berjalan sekali.

Selanjutnya, zipmengambil sejumlah iterable ( urutan adalah iterable ) dan membuat tupel dari elemen ke-i dari masing-masing urutan masukan. Karena kedua iterator identik dalam kasus kita, zip memindahkan iterator yang sama dua kali untuk setiap tupel 2-elemen keluaran.

In [41]: help(zip)
Help on built-in function zip in module __builtin__:

zip(...)
    zip(seq1 [, seq2 [...]]) -> [(seq1[0], seq2[0] ...), (...)]

    Return a list of tuples, where each tuple contains the i-th element
    from each of the argument sequences.  The returned list is truncated
    in length to the length of the shortest argument sequence.

Operator unpacking ( *) memastikan bahwa iterator berjalan hingga habis yang dalam hal ini sampai tidak ada cukup input untuk membuat tupel 2 elemen.

Ini dapat diperluas ke nilai apa pun ndan zip(*[iter(s)]*n)berfungsi seperti yang dijelaskan.

akhan
sumber
Maaf karena lambat. Tapi bisakah Anda menjelaskan "iterator yang sama dua kali karena * 2 python syntactic sugar. Iterator juga hanya berjalan sekali." tolong berpisah? Kalau begitu kok hasilnya bukan [("A", "A") ....]? Terima kasih.
Bowen Liu
@BowenLiu *hanyalah kemudahan untuk menduplikasi objek. Cobalah dengan skalar dan kemudian dengan daftar. Juga mencoba print(*zip(*[iter("ABCDEFG")]*2))vs print(*zip(*[iter("ABCDEFG"), iter("ABCDEFG")])). Kemudian mulailah membagi keduanya menjadi langkah-langkah yang lebih kecil untuk melihat apa sebenarnya objek iterator dalam kedua pernyataan tersebut.
akhan