Podwójna iteracja w rozumieniu listy


226

W Pythonie możesz mieć wiele iteratorów ze zrozumieniem listy, takich jak

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

dla niektórych odpowiednich sekwencji a i b. Zdaję sobie sprawę z semantyki zagnieżdżonej pętli w zrozumieniu list Pythona.

Moje pytanie brzmi: czy jeden iterator w rozumieniu może odnosić się do drugiego? Innymi słowy: czy mogę mieć coś takiego:

[x for x in a for a in b]

gdzie aktualna wartość zewnętrznej pętli jest iteratorem wewnętrznej?

Na przykład, jeśli mam listę zagnieżdżoną:

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

jakie byłoby wyrażenie ze zrozumieniem listy, aby osiągnąć ten wynik:

[1,2,3,4]

?? (Proszę wymienić tylko odpowiedzi na zrozumienie, ponieważ właśnie tego chcę się dowiedzieć).

Odpowiedzi:


178

Aby odpowiedzieć na twoje pytanie własną sugestią:

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

Podczas gdy prosiłeś o odpowiedzi na listy, pozwól mi również wskazać doskonałą itertools.chain ():

>>> from itertools import chain
>>> list(chain.from_iterable(a))
>>> list(chain(*a)) # If you're using python < 2.6

11
[x for b in a for x in b]To zawsze dotyczyło Pythona. Ta składnia jest bardzo wsteczna. Ogólna forma x for x in yzawsze ma zmienną bezpośrednio po for, zasila wyrażenie po lewej stronie for. Gdy tylko dokonasz podwójnego zrozumienia, twoja ostatnio iterowana zmienna jest nagle tak „daleko”. Jest niezręczny i w ogóle nie czyta naturalnie
Cruncher

169

Mam nadzieję, że to pomaga komuś innemu, ponieważ a,b,x,ynie ma dla mnie większego znaczenia! Załóżmy, że masz tekst pełen zdań i potrzebujesz szeregu słów.

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

Lubię rozumieć listę jako rozciąganie kodu w poziomie.

Spróbuj podzielić to na:

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

Przykład:

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

Działa to również w przypadku generatorów

>>> 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?

8
„W informatyce występują tylko dwa trudne problemy: unieważnienie pamięci podręcznej i nadawanie nazw”. - Phil Karlton
cezar

To świetna odpowiedź, ponieważ sprawia, że ​​cały problem jest mniej abstrakcyjny! Dziękuję Ci!
A. Blesius

Zastanawiałem się, czy możesz zrobić to samo z trzema poziomami abstrakcji w rozumieniu listy? Podobnie jak rozdziały w tekście, zdania w rozdziałach i słowa w zdaniach?
Kapitan Fogetti

123

Rany, chyba znalazłem odpowiedź: nie dbałem wystarczająco o to, która pętla jest wewnętrzna, a która zewnętrzna. Zrozumienie listy powinno wyglądać następująco:

[x for b in a for x in b]

aby uzyskać pożądany wynik, i tak, jedna bieżąca wartość może być iteratorem następnej pętli.


67
Składnia ze zrozumieniem listy nie jest jedną z głównych zalet Pythona.
Glenn Maynard

2
@Glenn Tak, łatwo się zawija w przypadku więcej niż prostych wyrażeń.
ThomasH

1
Ew. Nie jestem pewien, czy jest to „zwykłe” użycie do interpretacji list, ale bardzo niefortunne jest to, że tworzenie łańcuchów w Pythonie jest tak paskudne.
Matt Joiner

14
Wygląda bardzo czysto, jeśli umieścisz nowe wiersze przed każdym „za”.
Nick Garvey

16
Wow, jest to całkowicie odwrotne do tego, co ma sens w mojej głowie.
obskyr

51

Kolejność iteratorów może wydawać się sprzeczna z intuicją.

Weź na przykład: [str(x) for i in range(3) for x in foo(i)]

Rozłóżmy to:

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)

4
Co za otwieracz do oczu !!
nehem

Rozumiem, że powodem tego jest to, że „pierwsza wymieniona iteracja jest najwyższą iteracją, która zostałaby wpisana, gdyby wyrażenie zostało napisane jako zagnieżdżone dla pętli”. Jest to sprzeczne z intuicją, ponieważ pętla OUTER (najwyższa, jeśli została napisana jako zagnieżdżona pętla for) pojawia się po WEWNĄTRZ listy / dict w nawiasach kwadratowych (obiekt rozumiany). I odwrotnie, pętla WEWNĘTRZNA (najbardziej wewnętrzna, gdy jest napisana jako zagnieżdżona pętla for) jest dokładnie najbardziej prawą pętlą w zrozumieniu i w ten sposób pojawia się na ZEWNĄTRZ zrozumienia.
Zach Siegel,

Abstrakcyjnie napisany mamy [(output in loop 2) (loop 1) (loop 2)]z (loop 1) = for i in range(3)i (loop 2) = for x in foo(i):a (output in loop 2) = str(x).
Qaswed

20

ThomasH dodał już dobrą odpowiedź, ale chcę pokazać, co się stanie:

>>> 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]

Myślę, że Python analizuje zrozumienie listy od lewej do prawej. Oznacza to, że pierwsza forwystępująca pętla zostanie wykonana jako pierwsza.

Drugim „problemem” jest to, że b„wyciekają” ze zrozumienia listy. Po pierwszym udanym zrozumieniu listy b == [3, 4].


3
Ciekawy punkt Byłem zaskoczony tym:x = 'hello'; [x for x in xrange(1,5)]; print x # x is now 4
grinch

2
Ten wyciek został naprawiony w Pythonie 3: stackoverflow.com/questions/4198906/…
Denilson Sá Maia

10

Jeśli chcesz zachować tablicę wielowymiarową, należy zagnieździć nawiasy tablicowe. patrz przykład poniżej, w którym jeden jest dodawany do każdego elementu.

>>> 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]

8

Ta technika pamięci bardzo mi pomaga:

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

A teraz możesz pomyśleć o R eturn + O u -loop, jako o jedynej R Oght O ania

Znając powyższe, kolejność w liście wyczerpująca nawet dla 3 pętli wydaje się łatwa:


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)]

ponieważ powyższe jest po prostu:

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

w przypadku iteracji jednej zagnieżdżonej listy / struktury technika jest taka sama: az pytania:

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

dla siebie zagnieżdżony poziom

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]

i tak dalej


Dzięki, ale to, co opisujesz, jest w rzeczywistości prostym przypadkiem, w którym zaangażowane iteratory są niezależne. W twoim przykładzie możesz użyć iteratorów w dowolnej kolejności i uzyskać tę samą listę wyników (porządkowanie modulo). Sprawa, która mnie bardziej zainteresowała, dotyczyła zagnieżdżonych list, w których jeden iterator staje się iteracją następnego.
ThomasH

@ThomasH: kolejność pętli zdefiniowana pogrubioną czcionką dokładnie odpowiada twoim potrzebom. Na dole dodano przykład obejmujący dane i jeszcze jeden przykład z dodatkowym poziomem zagnieżdżonym.
Sławomir Lenart

5

Wydaje mi się, że łatwiej to zrozumieć

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

result: [1, 2, 3, 4]

3

Dodatkowo, możesz użyć tej samej zmiennej dla elementu listy wejściowej, do którego jest aktualnie uzyskiwany dostęp, oraz dla elementu wewnątrz tego elementu. Może to jednak nawet uczynić go bardziej (listowym) niezrozumiałym.

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

Najpierw for x in inputjest analizowany, co prowadzi do jednej listy członków danych wejściowych, następnie Python przechodzi przez drugą część, for x in xpodczas której wartość x jest nadpisywana przez bieżący element, do którego ma dostęp, a następnie pierwsza xokreśla, co chcemy zwrócić.


1

Ta funkcja flatten_nlevel wywołuje rekurencyjnie zagnieżdżoną listę1, aby ukryć się na jednym poziomie. Wypróbuj to

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)

wynik:

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

1
Ok, pytanie dotyczyło w szczególności zrozumienia listy, a spłaszczenie listy było tylko przykładem. Ale zakładam, że twój uogólniony spłaszczacz listy musiałby się nazywać rekurencyjnie. Więc to chyba bardziej flatten_nlevel(sublist, flat_list), prawda ?!
ThomasH
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.