Doppelte Iteration beim Listenverständnis

226

In Python können Sie mehrere Iteratoren in einem Listenverständnis haben, wie z

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

für einige geeignete Sequenzen a und b. Mir ist die Semantik verschachtelter Schleifen in Pythons Listenverständnis bekannt.

Meine Frage ist: Kann sich ein Iterator im Verständnis auf den anderen beziehen? Mit anderen Worten: Könnte ich so etwas haben:

[x for x in a for a in b]

Wo ist der aktuelle Wert der äußeren Schleife der Iterator der inneren?

Wenn ich beispielsweise eine verschachtelte Liste habe:

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

Was wäre der Ausdruck für das Listenverständnis, um dieses Ergebnis zu erzielen:

[1,2,3,4]

?? (Bitte nur Verständnisantworten auflisten, da ich dies herausfinden möchte).

ThomasH
quelle

Antworten:

178

Um Ihre Frage mit Ihrem eigenen Vorschlag zu beantworten:

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

Während Sie nach Antworten zum Listenverständnis gefragt haben, möchte ich auch auf die hervorragenden itertools.chain () hinweisen:

>>> from itertools import chain
>>> list(chain.from_iterable(a))
>>> list(chain(*a)) # If you're using python < 2.6
Cide
quelle
10
[x for b in a for x in b]Dies hat immer über Python abgehört. Diese Syntax ist so rückwärts. Die allgemeine Form von hat x for x in yimmer die Variable direkt nach dem for, füttert den Ausdruck links vom for. Sobald Sie ein doppeltes Verständnis haben, ist Ihre zuletzt iterierte Variable plötzlich so weit. Es ist umständlich und liest sich überhaupt nicht natürlich
Cruncher
167

Ich hoffe das hilft jemand anderem, da a,b,x,yich nicht viel Bedeutung habe! Angenommen, Sie haben einen Text voller Sätze und möchten eine Reihe von Wörtern.

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

Ich stelle mir Listenverständnis gerne als horizontales Strecken von Code vor.

Versuchen Sie es in:

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

Beispiel:

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

Dies funktioniert auch für Generatoren

>>> 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
quelle
8
"In der Informatik gibt es nur zwei schwierige Probleme: die Ungültigmachung des Caches und die Benennung von Dingen." - Phil Karlton
Cezar
Dies ist eine großartige Antwort, da dadurch das gesamte Problem weniger abstrakt wird! Danke dir!
A. Blesius
Ich habe mich gefragt, ob Sie dasselbe mit drei Abstraktionsebenen in einem Listenverständnis tun können. Wie Kapitel im Text, Sätze in Kapiteln und Wörter in Sätzen?
Captain Fogetti
123

Ich glaube, ich habe die Antwort gefunden: Ich habe nicht genug darauf geachtet, welche Schleife innen und welche außen ist. Das Listenverständnis sollte wie folgt aussehen:

[x for b in a for x in b]

Um das gewünschte Ergebnis zu erhalten, und ja, kann ein aktueller Wert der Iterator für die nächste Schleife sein.

ThomasH
quelle
67
Die Syntax des Listenverständnisses ist nicht einer der Glanzpunkte von Python.
Glenn Maynard
2
@Glenn Ja, es wird leicht für mehr als einfache Ausdrücke verwickelt.
ThomasH
1
Ew. Ich bin nicht sicher, ob dies die "übliche" Verwendung für das Listenverständnis ist, aber es ist sehr bedauerlich, dass die Verkettung in Python so unangenehm ist.
Matt Joiner
14
Es sieht sehr sauber aus, wenn Sie vor jedem 'für' Zeilenumbrüche setzen.
Nick Garvey
15
Wow, das ist völlig umgekehrt zu dem, was in meinem Kopf Sinn macht.
Obskyr
51

Die Reihenfolge der Iteratoren scheint möglicherweise nicht intuitiv zu sein.

Nehmen Sie zum Beispiel: [str(x) for i in range(3) for x in foo(i)]

Zerlegen wir es:

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
quelle
4
Was für ein Augenöffner!
Nehem
Mein Verständnis ist, dass der Grund dafür ist, dass "die erste aufgeführte Iteration die oberste Iteration ist, die eingegeben würde, wenn das Verständnis als verschachtelt für Schleifen geschrieben würde". Der Grund dafür ist, dass die OUTER-Schleife (am obersten, wenn sie als verschachtelte for-Schleifen geschrieben ist) INSIDE in der Liste / im Diktat in Klammern (verstandenes Objekt) angezeigt wird. Umgekehrt ist die INNER-Schleife (am innersten, wenn sie als verschachtelte for-Schleifen geschrieben ist) genau die am weitesten rechts stehende Schleife in einem Verständnis und erscheint auf diese Weise AUSSERHALB des Verständnisses.
Zach Siegel
Abstrakt geschrieben haben wir [(output in loop 2) (loop 1) (loop 2)]mit (loop 1) = for i in range(3)und (loop 2) = for x in foo(i):und (output in loop 2) = str(x).
Qaswed
20

ThomasH hat bereits eine gute Antwort hinzugefügt, aber ich möchte zeigen, was passiert:

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

Ich denke, Python analysiert das Listenverständnis von links nach rechts. Dies bedeutet, dass die erste auftretende forSchleife zuerst ausgeführt wird.

Das zweite "Problem" dabei ist, dass es baus dem Listenverständnis "durchgesickert" wird. Nach dem ersten erfolgreichen Listenverständnis b == [3, 4].

Martin Thoma
quelle
3
Interessanter Punkt. Ich war überrascht:x = 'hello'; [x for x in xrange(1,5)]; print x # x is now 4
grinch
2
Dieses Leck wurde in Python 3 behoben
Denilson Sá Maia
10

Wenn Sie das mehrdimensionale Array beibehalten möchten, sollten Sie die Array-Klammern verschachteln. Siehe Beispiel unten, in dem jedem Element eines hinzugefügt wird.

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

Diese Speichertechnik hilft mir sehr:

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

Und jetzt können Sie sich R eturn + O uter-loop als den einzigen R ight O rder vorstellen

Wie oben bekannt, scheint die Reihenfolge in der Liste, die auch für 3 Schleifen umfassend ist, einfach zu sein:


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

weil das obige nur ein:

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

Für die Iteration einer verschachtelten Liste / Struktur ist die Technik dieselbe: für aaus der Frage:

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

für einander verschachtelte Ebene

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]

und so weiter

Sławomir Lenart
quelle
Danke, aber was Sie beschreiben, ist eigentlich der einfache Fall, in dem die beteiligten Iteratoren unabhängig sind. In Ihrem Beispiel könnten Sie die Iteratoren in beliebiger Reihenfolge verwenden und würden dieselbe Ergebnisliste erhalten (Modulo-Reihenfolge). Der Fall, an dem ich mehr interessiert war, betraf verschachtelte Listen, bei denen ein Iterator zum iterierbaren des nächsten wird.
ThomasH
@ThomasH: Die fettgedruckte Reihenfolge der Schleife entspricht genau Ihren Anforderungen. Unten wurde ein Beispiel hinzugefügt, um Ihre Daten abzudecken, und ein weiteres Beispiel mit einer zusätzlichen verschachtelten Ebene.
Sławomir Lenart
5

Ich denke, das ist leichter zu verstehen

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

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

Darüber hinaus können Sie für das Mitglied der Eingabeliste, auf das derzeit zugegriffen wird, und für das Element in diesem Element dieselbe Variable verwenden . Dies könnte es jedoch noch verständlicher machen.

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

Zuerst for x in inputwird ausgewertet, was zu einer Mitgliederliste der Eingabe führt. Dann geht Python durch den zweiten Teil, for x in xin dem der x-Wert durch das aktuelle Element, auf das zugegriffen wird, überschrieben wird. Dann xdefiniert der erste, was wir zurückgeben möchten.

simP
quelle
1

Diese Funktion flatten_nlevel ruft die verschachtelte Liste1 rekursiv auf, um sie auf eine Ebene zu verbergen. Probieren Sie es aus

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)

Ausgabe:

[1, 1, 2, 3, 4, 6, 4, 5]
Ravibeli
quelle
1
Ok, die Frage betraf insbesondere das Listenverständnis, und die Listenreduzierung war nur ein Beispiel. Aber ich gehe davon aus, dass sich Ihr generalisierter Listenabflacher rekursiv aufrufen müsste. Also ist es wahrscheinlich eher so flatten_nlevel(sublist, flat_list), oder?!
ThomasH