Doppia iterazione nella comprensione dell'elenco


226

In Python puoi avere più iteratori in una comprensione di lista, come

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

per alcune sequenze adatte a e b. Sono a conoscenza della semantica del ciclo nidificato delle comprensioni dell'elenco di Python.

La mia domanda è: un iteratore nella comprensione può riferirsi all'altro? In altre parole: potrei avere qualcosa del genere:

[x for x in a for a in b]

dove il valore corrente dell'anello esterno è l'iteratore dell'interiore?

Ad esempio, se ho un elenco nidificato:

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

quale sarebbe l'espressione di comprensione dell'elenco per ottenere questo risultato:

[1,2,3,4]

?? (Elenca solo le risposte di comprensione, poiché questo è quello che voglio scoprire).

Risposte:


178

Per rispondere alla tua domanda con il tuo suggerimento:

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

Mentre hai chiesto risposte per la comprensione dell'elenco, vorrei anche sottolineare l'eccellente 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]Questo è sempre stato un problema per Python. Questa sintassi è così all'indietro. La forma generale di x for x in yha sempre la variabile direttamente dopo il for, si nutre dell'espressione a sinistra del for. Non appena fai una doppia comprensione, la tua variabile iterata più recente è improvvisamente così "lontana". È imbarazzante e non legge affatto in modo naturale
Cruncher,

170

Spero che questo aiuti qualcun altro poiché a,b,x,ynon ha molto significato per me! Supponi di avere un testo pieno di frasi e desideri una serie di parole.

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

Mi piace pensare alla comprensione dell'elenco come ad allungare il codice in senso orizzontale.

Prova a suddividerlo in:

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

Esempio:

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

Questo funziona anche per i generatori

>>> 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
"Ci sono solo due problemi gravi in ​​Informatica: invalidare la cache e denominare le cose." - Phil Karlton
cezar

Questa è un'ottima risposta poiché rende l'intero problema meno astratto! Grazie!
A. Blesius

Mi chiedevo, puoi fare lo stesso con tre livelli di astrazione nella comprensione di un elenco? Come i capitoli nel testo, le frasi nei capitoli e le parole nelle frasi?
Capitano Fogetti il

123

Accidenti, immagino di aver trovato la risposta: non mi preoccupavo abbastanza di quale anello fosse interno e quale fosse esterno. La comprensione dell'elenco dovrebbe essere come:

[x for b in a for x in b]

per ottenere il risultato desiderato, e sì, un valore corrente può essere l'iteratore per il ciclo successivo.


67
La sintassi della comprensione dell'elenco non è uno dei punti brillanti di Python.
Glenn Maynard,

2
@Glenn Sì, diventa facilmente contorto per più di semplici espressioni.
ThomasH,

1
Ew. Non sono sicuro che questo sia il "solito" uso per la comprensione delle liste, ma è davvero un peccato che il concatenamento sia così brutto in Python.
Matt Joiner,

14
Sembra molto pulito se metti le nuove righe prima di ogni 'per'.
Nick Garvey,

16
Wow, questo è completamente contrario a ciò che ha senso nella mia testa.
obskyr,

51

L'ordine degli iteratori può sembrare controintuitivo.

Prendi ad esempio: [str(x) for i in range(3) for x in foo(i)]

Decomponiamo:

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
Che colpo d'occhio !!
Nehem,

La mia comprensione è che la ragione di ciò è che "la prima iterazione elencata è l'iterazione più elevata che verrebbe digitata se la comprensione fosse scritta come annidata per i loop". La ragione per cui ciò è controintuitivo è che il loop OUTER (il più in alto se scritto come loop for nidificati) appare all'interno dell'elenco / dict tra parentesi (oggetto compreso). Al contrario, il ciclo INNER (il più interno se scritto come loop for nidificati) è precisamente il loop più a destra in una comprensione, e in questo modo appare all'esterno della comprensione.
Zach Siegel,

Scritto in astratto abbiamo [(output in loop 2) (loop 1) (loop 2)]con (loop 1) = for i in range(3)e (loop 2) = for x in foo(i):e (output in loop 2) = str(x).
Risolto il

20

ThomasH ha già aggiunto una buona risposta, ma voglio mostrare cosa succede:

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

Immagino che Python analizzi la comprensione dell'elenco da sinistra a destra. Questo significa che il primofor loop che si verifica verrà eseguito per primo.

Il secondo "problema" di questo è che bviene "trapelato" dalla comprensione dell'elenco. Dopo la prima comprensione riuscita dell'elenco b == [3, 4].


3
Punto interessante Sono stato sorpreso da questo:x = 'hello'; [x for x in xrange(1,5)]; print x # x is now 4
grinch

2
Questa perdita è stata corretta in Python 3: stackoverflow.com/questions/4198906/…
Denilson Sá Maia,

10

Se si desidera mantenere l'array multidimensionale, è necessario nidificare le parentesi dell'array. vedi esempio sotto dove uno viene aggiunto ad ogni elemento.

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

Questa tecnica di memoria mi aiuta molto:

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

E ora puoi pensare a R eturn + O uter-loop come all'unica R ight O rder

Sapendo sopra, l'ordine in elenco completo anche per 3 loop sembra facile:


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

perché quanto sopra è solo un:

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

per iterare una lista / struttura nidificata, la tecnica è la stessa: per adalla domanda:

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

l'uno per l'altro livello nidificato

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]

e così via


Grazie, ma quello che descrivi è in realtà il semplice caso in cui gli iteratori coinvolti sono indipendenti. In effetti, nel tuo esempio potresti usare gli iteratori in qualsiasi ordine e ottenere lo stesso elenco di risultati (ordinamento modulo). Il caso a cui ero più interessato era con elenchi nidificati in cui un iteratore diventa l'iterabile del successivo.
ThomasH

@ThomasH: l'ordine del loop definito in grassetto è esattamente per le tue esigenze. In basso è stato aggiunto un esempio per coprire i dati e un altro esempio con livello nidificato aggiuntivo.
Sławomir Lenart,

5

Sento che è più facile da capire

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

result: [1, 2, 3, 4]

3

Inoltre, è possibile utilizzare solo la stessa variabile per il membro dell'elenco di input a cui si accede attualmente e per l'elemento all'interno di questo membro. Tuttavia, questo potrebbe persino renderlo più (elenco) incomprensibile.

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

for x in inputViene valutato innanzitutto , portando a un elenco di membri dell'input, quindi Python percorre la seconda parte for x in xdurante la quale il valore x viene sovrascritto dall'elemento corrente a cui accede, quindi il primo xdefinisce ciò che vogliamo restituire.


1

Questa funzione flatten_nlevel chiama ricorsivamente l'elenco nidificato1 per passare a un livello. Prova questo

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)

produzione:

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

1
Ok, la domanda riguardava in particolare la comprensione dell'elenco e l'appiattimento dell'elenco era solo un esempio. Ma suppongo che il tuo appiattitore di elenchi generalizzato dovrebbe chiamarsi in modo ricorsivo. Quindi è probabilmente più simile flatten_nlevel(sublist, flat_list), giusto ?!
ThomasH
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.