2009-07-02 11 views
52

ragazzi. Sto cercando di trovare la soluzione più elegante per un problema e mi chiedevo se Python avesse qualcosa di integrato per quello che sto cercando di fare.python list comprehensions; comprimere un elenco di elenchi?

Quello che sto facendo è questo. Ho una lista, A, e ho una funzione f che accetta un elemento e restituisce una lista. Posso usare una lista di comprensione per convertire tutto in A in questo modo;

[f(a) for a in A] 

Ma questo restituisce un elenco di elenchi;

[a1,a2,a3] => [[b11,b12],[b21,b22],[b31,b32]] 

Quello che voglio veramente è ottenere la lista appiattita;

[b11,b12,b21,b22,b31,b32] 

Ora, altri linguaggi ce l'hanno; è tradizionalmente chiamato flatmap in linguaggi di programmazione funzionale e .Net lo chiama SelectMany. Python ha qualcosa di simile? C'è un modo pulito per mappare una funzione su un elenco e appiattire il risultato?

Il vero problema che sto cercando di risolvere è questo; iniziando con un elenco di directory, trova tutte le sottodirectory. così;

import os 
dirs = ["c:\\usr", "c:\\temp"] 
subs = [os.listdir(d) for d in dirs] 
print subs 

currentliy mi dà una lista-di-liste, ma voglio davvero una lista.

risposta

74

Si possono avere nidificato iterazioni in una lista unica di comprensione:

[filename for path in dirs for filename in os.listdir(path)] 
+25

Anche se intelligente, difficile da capire e poco leggibile. –

+1

In realtà non risponde alla domanda come richiesto. Questo è piuttosto una soluzione alternativa per non incontrare il problema in primo luogo. Cosa succede se hai già una lista di liste. Ad esempio, cosa succede se il tuo elenco di elenchi è il risultato della funzione mappa del modulo multiprocesso? Forse la soluzione itertools o la soluzione di riduzione è la migliore. – Dave31415

+8

Dave31415: '[voce per l'elenco in listofliste per la voce in elenco]' – rampion

3

Si potrebbe provare itertools.chain(), in questo modo:

import itertools 
import os 
dirs = ["c:\\usr", "c:\\temp"] 
subs = list(itertools.chain(*[os.listdir(d) for d in dirs])) 
print subs 

itertools.chain() restituisce un iteratore, da qui il passaggio alla list().

5
subs = [] 
map(subs.extend, (os.listdir(d) for d in dirs)) 

(risposta, ma Ants è migliore; +1 per lui)

+0

Utilizzando ridurre (o somma, che consente di risparmiare molti personaggi e un'importazione ;-) per questo è solo sbagliato - si mantiene inutilmente gettando via le vecchie liste a creane uno nuovo per ogni d. @Ants ha la risposta giusta (smart di @Steve per accettarlo!). –

+0

@Alex: buon punto. Fisso. – RichieHindle

+0

In generale non si può dire che questa sia una soluzione sbagliata. Dipende se la prestazione è addirittura un problema. Semplice è meglio a meno che non ci sia un motivo per ottimizzare. Ecco perché il metodo di riduzione potrebbe essere la soluzione migliore per molti problemi. Ad esempio, hai una funzione lenta che produce un elenco di poche centinaia di oggetti.Vuoi accelerarlo usando la funzione 'mappa' di multiprocessing. Quindi crei 4 processi e utilizzi riduci a mappa piana. In questo caso la funzione di riduzione è buona e molto leggibile. Detto questo, è bene che tu sottolinei perché questo può non essere ottimale. Ma non è sempre subottimale. – Dave31415

37

È possibile trovare una buona risposta in itertools' recipes:

def flatten(listOfLists): 
    return list(chain.from_iterable(listOfLists)) 

(Nota: richiede Python 2.6+)

+0

Lo stesso approccio può essere utilizzato per definire la flatmap, come proposto da [questa risposta] (https://stackoverflow.com/a/20037408/1048186) e questo [post di blog esterno] (http://naiquevin.github.io /a-look-at-some-of-pythons-useful-itertools.html) –

3

Google mi ha portato soluzione successiva:

def flatten(l): 
    if isinstance(l,list): 
     return sum(map(flatten,l)) 
    else: 
     return l 
+2

Sarebbe un po 'meglio se gestisse anche le espressioni generatrici, e sarebbe molto meglio se spiegassi come usarlo ... – ephemient

14

Si potrebbe solo fare il semplice:

subs = [] 
for d in dirs: 
    subs.extend(os.listdir(d)) 
+0

Sì, questo va bene (anche se non abbastanza buono come @Ants ') quindi sono Dare un +1 per onorare la sua semplicità! –

10

È possibile concatenare elenchi utilizzando l'operatore di aggiunta normale:

>>> [1, 2] + [3, 4] 
[1, 2, 3, 4] 

La funzione built-in sum aggiungerà i numeri in sequenza e può opzionalmente partire da un valore specifico:

>>> sum(xrange(10), 100) 
145 

Unire il sopra per appiattire una lista di liste:

>>> sum([[1, 2], [3, 4]], []) 
[1, 2, 3, 4] 

È ora possibile definire il flatmap:

>>> def flatmap(f, seq): 
... return sum([f(s) for s in seq], []) 
... 
>>> flatmap(range, [1,2,3]) 
[0, 0, 1, 0, 1, 2] 

Modifica: Ho appena visto la critica nei commenti per another answer e credo sia corretto che Python costruirà inutilmente e raccogliere molti elenchi più piccoli con questa soluzione. Quindi la cosa migliore che si può dire a questo proposito è che è molto semplice e concisa, se siete abituati a programmazione funzionale :-)

+0

Risposta eccellente – Phil

45
>>> listOfLists = [[1, 2],[3, 4, 5], [6]] 
>>> reduce(list.__add__, listOfLists) 
[1, 2, 3, 4, 5, 6] 

Sto indovinando la soluzione itertools è più efficiente di questo, ma questo mi sembra molto pitone ed evita di dover importare una libreria solo per una singola operazione di lista.

+3

Questa è sicuramente la soluzione migliore –

7
import itertools 
x=[['b11','b12'],['b21','b22'],['b31']] 
y=list(itertools.chain(*x)) 
print y 

itertools lavoreranno da python2.3 e una maggiore

14

La domanda proposta flatmap. Alcune implementazioni sono proposte ma potrebbero non essere necessarie la creazione di elenchi intermedi. Ecco un'implementazione basata su iteratori.

def flatmap(func, *iterable): 
    return itertools.chain.from_iterable(map(func, *iterable)) 

In [148]: list(flatmap(os.listdir, ['c:/mfg','c:/Intel'])) 
Out[148]: ['SPEC.pdf', 'W7ADD64EN006.cdr', 'W7ADD64EN006.pdf', 'ExtremeGraphics', 'Logs'] 

In Python 2.x, utilizzare itertools.map al posto di map.

0
If listA=[list1,list2,list3] 
flattened_list=reduce(lambda x,y:x+y,listA) 

Questo farà.

+0

Questa è una soluzione molto inefficiente se le sottoliste sono grandi. L'operatore '+' tra due liste è O (n + m) –

1
def flat_list(arr): 
    send_back = [] 
    for i in arr: 
     if type(i) == list: 
      send_back += flat_list(i) 
     else: 
      send_back.append(i) 
    return send_back 
1

È possibile utilizzare pyxtension:

from pyxtension.streams import stream 
stream([ [1,2,3], [4,5], [], [6] ]).flatMap() == range(7) 
Problemi correlati