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.