Iterasi Ganda dalam Pemahaman Daftar

226

Dalam Python, Anda dapat memiliki beberapa iterator dalam pemahaman daftar, seperti

[(x,y) for x in a for y in b]

untuk beberapa urutan yang sesuai a dan b. Saya menyadari semantik loop bersarang dari pemahaman daftar Python.

Pertanyaan saya adalah: Bisakah satu iterator dalam pemahaman merujuk ke yang lain? Dengan kata lain: Dapatkah saya memiliki sesuatu seperti ini:

[x for x in a for a in b]

di mana nilai saat ini dari loop luar adalah iterator bagian dalam?

Sebagai contoh, jika saya memiliki daftar bersarang:

a=[[1,2],[3,4]]

seperti apa ekspresi pemahaman daftar untuk mencapai hasil ini:

[1,2,3,4]

?? (Tolong cantumkan jawaban pemahaman saja, karena ini yang ingin saya ketahui).

ThomasH
sumber

Jawaban:

178

Untuk menjawab pertanyaan Anda dengan saran Anda sendiri:

>>> [x for b in a for x in b] # Works fine

Sementara Anda meminta jawaban daftar pemahaman, izinkan saya juga menunjukkan itertools.chain () yang sangat baik:

>>> from itertools import chain
>>> list(chain.from_iterable(a))
>>> list(chain(*a)) # If you're using python < 2.6
Cide
sumber
11
[x for b in a for x in b]Ini selalu disadap tentang python. Sintaks ini sangat mundur. Bentuk umum x for x in yselalu memiliki variabel langsung setelah untuk, diumpankan ke ekspresi di sebelah kiri untuk. Segera setelah Anda melakukan pemahaman ganda, variabel iterated terakhir Anda tiba-tiba begitu "jauh". Ini aneh, dan tidak membaca sama sekali
Cruncher
170

Saya harap ini membantu orang lain karena a,b,x,ytidak ada artinya bagi saya! Misalkan Anda memiliki teks yang penuh dengan kalimat dan Anda ingin berbagai kata.

# Without list comprehension
list_of_words = []
for sentence in text:
    for word in sentence:
       list_of_words.append(word)
return list_of_words

Saya suka menganggap daftar pemahaman sebagai peregangan kode secara horizontal.

Coba pisahkan menjadi:

# List Comprehension 
[word for sentence in text for word in sentence]

Contoh:

>>> text = (("Hi", "Steve!"), ("What's", "up?"))
>>> [word for sentence in text for word in sentence]
['Hi', 'Steve!', "What's", 'up?']

Ini juga berfungsi untuk generator

>>> text = (("Hi", "Steve!"), ("What's", "up?"))
>>> gen = (word for sentence in text for word in sentence)
>>> for word in gen: print(word)
Hi
Steve!
What's
up?
Skam
sumber
8
"Hanya ada dua masalah sulit dalam Ilmu Komputer: pembatalan cache dan penamaan hal-hal." - Phil Karlton
cezar
Ini adalah jawaban yang bagus karena membuat seluruh masalah menjadi kurang abstrak! Terima kasih!
A. Blesius
Saya bertanya-tanya, dapatkah Anda melakukan hal yang sama dengan tiga level abstraksi dalam pemahaman daftar? Seperti bab dalam teks, kalimat dalam bab dan kata dalam kalimat?
Kapten Fogetti
123

Wah, saya kira saya menemukan anwser: Saya tidak cukup peduli tentang loop mana yang dalam dan mana yang luar. Pemahaman daftar harus seperti:

[x for b in a for x in b]

untuk mendapatkan hasil yang diinginkan, dan ya, satu nilai saat ini dapat menjadi iterator untuk loop berikutnya.

ThomasH
sumber
67
Sintaksis pemahaman daftar bukan salah satu dari titik terang Python.
Glenn Maynard
2
@ Glenn Ya, itu mudah berbelit-belit untuk lebih dari ekspresi sederhana.
ThomasH
1
Ew. Saya tidak yakin ini adalah "biasa" digunakan untuk daftar pemahaman, tetapi sangat disayangkan bahwa rantai sangat jahat di Python.
Matt Joiner
14
Ini terlihat sangat bersih jika Anda meletakkan baris baru sebelum setiap 'untuk'.
Nick Garvey
16
Wow, ini benar-benar terbalik dengan apa yang masuk akal di kepalaku.
obskyr
51

Urutan iterator mungkin tampak kontra-intuitif.

Ambil contoh: [str(x) for i in range(3) for x in foo(i)]

Mari kita uraikan:

def foo(i):
    return i, i + 0.5

[str(x)
    for i in range(3)
        for x in foo(i)
]

# is same as
for i in range(3):
    for x in foo(i):
        yield str(x)
Dima Tisnek
sumber
4
Sungguh pembuka mata !!
nehem
Pemahaman saya adalah bahwa alasan untuk ini adalah bahwa "iterasi pertama yang tercantum adalah iterasi paling atas yang akan diketik jika pemahamannya ditulis sebagai bersarang untuk loop". Alasan ini berlawanan dengan intuisi adalah bahwa loop OUTER (paling atas jika dituliskan sebagai for-loop) muncul di bagian dalam daftar / kurung kurung (objek yang dipahami). Sebaliknya, loop INNER (paling dalam ketika ditulis sebagai nested for-loop) justru merupakan loop paling kanan dalam pemahaman, dan dengan cara itu muncul di LUAR pemahaman.
Zach Siegel
Ditulis secara abstrak yang kita miliki [(output in loop 2) (loop 1) (loop 2)]dengan (loop 1) = for i in range(3)dan (loop 2) = for x in foo(i):dan (output in loop 2) = str(x).
Qaswed
20

ThomasH telah menambahkan jawaban yang bagus, tetapi saya ingin menunjukkan apa yang terjadi:

>>> a = [[1, 2], [3, 4]]
>>> [x for x in b for b in a]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'b' is not defined

>>> [x for b in a for x in b]
[1, 2, 3, 4]
>>> [x for x in b for b in a]
[3, 3, 4, 4]

Saya kira Python mem-parsing daftar pemahaman dari kiri ke kanan. Ini artinya, forloop pertama yang terjadi akan dieksekusi terlebih dahulu.

"Masalah" kedua adalah bahwa hal ini b"bocor" keluar dari pemahaman daftar. Setelah pemahaman daftar sukses pertama b == [3, 4].

Martin Thoma
sumber
3
Poin yang menarik. Saya terkejut dengan hal ini:x = 'hello'; [x for x in xrange(1,5)]; print x # x is now 4
menyentak
2
Kebocoran ini diperbaiki dalam Python 3: stackoverflow.com/questions/4198906/...
Denilson Sá Maia
10

Jika Anda ingin mempertahankan array multi dimensi, Anda harus membuat kurung array. lihat contoh di bawah ini di mana satu ditambahkan ke setiap elemen.

>>> a = [[1, 2], [3, 4]]

>>> [[col +1 for col in row] for row in a]
[[2, 3], [4, 5]]

>>> [col +1 for row in a for col in row]
[2, 3, 4, 5]
steven
sumber
8

Teknik memori ini banyak membantu saya:

[ <RETURNED_VALUE> <OUTER_LOOP1> <INNER_LOOP2> <INNER_LOOP3> ... <OPTIONAL_IF> ]

Dan sekarang Anda bisa memikirkan R eturn + O uter-loop sebagai satu-satunya R ight O rder

Mengetahui di atas, urutan daftar lengkap bahkan untuk 3 loop sepertinya mudah:


c=[111, 222, 333]
b=[11, 22, 33]
a=[1, 2, 3]

print(
  [
    (i, j, k)                            # <RETURNED_VALUE> 
    for i in a for j in b for k in c     # in order: loop1, loop2, loop3
    if i < 2 and j < 20 and k < 200      # <OPTIONAL_IF>
  ]
)
[(1, 11, 111)]

karena di atas hanyalah:

for i in a:                         # outer loop1 GOES SECOND
  for j in b:                       # inner loop2 GOES THIRD
    for k in c:                     # inner loop3 GOES FOURTH
      if i < 2 and j < 20 and k < 200:
        print((i, j, k))            # returned value GOES FIRST

untuk iterasi satu daftar / struktur bersarang, tekniknya sama: untuk adari pertanyaan:

a = [[1,2],[3,4]]
[i2    for i1 in a      for i2 in i1]
which return [1, 2, 3, 4]

untuk satu tingkat bersarang lainnya

a = [[[1, 2], [3, 4]], [[5, 6], [7, 8, 9]], [[10]]]
[i3    for i1 in a      for i2 in i1     for i3 in i2]
which return [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

dan seterusnya

Sławomir Lenart
sumber
Terima kasih, tetapi apa yang Anda gambarkan sebenarnya adalah kasus sederhana di mana iterator yang terlibat independen. Bahkan, dalam contoh Anda, Anda dapat menggunakan iterator dalam urutan apa pun dan akan mendapatkan daftar hasil yang sama (pemesanan modulo). Kasus saya lebih tertarik adalah dengan daftar bersarang di mana satu iterator menjadi iterable dari yang berikutnya.
ThomasH
@ ThomasH: urutan loop yang didefinisikan dalam huruf tebal tepat untuk kebutuhan Anda. Di bagian bawah menambahkan contoh untuk menutupi data Anda dan satu lagi contoh dengan tingkat bersarang tambahan.
Sławomir Lenart
5

Saya merasa ini lebih mudah dimengerti

[row[i] for row in a for i in range(len(a))]

result: [1, 2, 3, 4]
Miao Li
sumber
3

Selain itu, Anda bisa menggunakan variabel yang sama untuk anggota daftar input yang saat ini diakses dan untuk elemen di dalam anggota ini. Namun, ini bahkan membuatnya lebih (daftar) tidak bisa dipahami.

input = [[1, 2], [3, 4]]
[x for x in input for x in x]

Pertama for x in inputdievaluasi, mengarah ke satu anggota daftar input, kemudian, Python berjalan melalui bagian kedua for x in xselama nilai-x ditimpa oleh elemen saat ini mengaksesnya, kemudian yang pertama xmenentukan apa yang ingin kita kembalikan.

SIMP
sumber
1

Fungsi flatten_nlevel ini memanggil daftar1 bersarang secara rekursif untuk rahasia ke satu level. Coba ini

def flatten_nlevel(list1, flat_list):
    for sublist in list1:
        if isinstance(sublist, type(list)):        
            flatten_nlevel(sublist, flat_list)
        else:
            flat_list.append(sublist)

list1 = [1,[1,[2,3,[4,6]],4],5]

items = []
flatten_nlevel(list1,items)
print(items)

keluaran:

[1, 1, 2, 3, 4, 6, 4, 5]
ravibeli
sumber
1
Ok, pertanyaannya terutama tentang pemahaman daftar, dan perataan daftar hanyalah sebuah contoh. Tapi saya berasumsi, daftar pemerataan umum Anda perlu menyebut dirinya secara rekursif. Jadi mungkin lebih seperti flatten_nlevel(sublist, flat_list), kan ?!
ThomasH