Come creare un elenco semplice dall'elenco degli elenchi?


3376

Mi chiedo se esiste un collegamento per creare un semplice elenco dall'elenco degli elenchi in Python.

Posso farlo in un forciclo, ma forse c'è un bel "one-liner"? L'ho provato con reduce(), ma viene visualizzato un errore.

Codice

l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
reduce(lambda x, y: x.extend(y), l)

Messaggio di errore

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <lambda>
AttributeError: 'NoneType' object has no attribute 'extend'

20
C'è una discussione approfondita di questo qui: rightfootin.blogspot.com/2006/09/more-on-python-flatten.html , discutendo diversi metodi di appiattimento di elenchi di elenchi nidificati arbitrariamente. Una lettura interessante!
RichieHindle,

6
Alcune altre risposte sono migliori, ma la ragione per cui la tua non riesce è che il metodo 'extension' restituisce sempre Nessuno. Per un elenco con lunghezza 2, funzionerà ma restituirà None. Per un elenco più lungo, utilizzerà i primi 2 argomenti, che restituisce None. Quindi continua con None.extend (<terzo argomento>), che causa questo
errore

La soluzione @ shawn-chin è la più pitonica qui, ma se hai bisogno di preservare il tipo di sequenza, supponi di avere una tupla di tuple piuttosto che un elenco di elenchi, quindi dovresti usare la riduzione (operator.concat, tuple_of_tuples). L'uso di operator.concat con le tuple sembra funzionare più velocemente di chain.from_iterables con l'elenco.
Meitham,

Risposte:


4799

Dato un elenco di elenchi l,

flat_list = [item for sublist in l for item in sublist]

che significa:

flat_list = []
for sublist in l:
    for item in sublist:
        flat_list.append(item)

è più veloce delle scorciatoie pubblicate finora. ( lè l'elenco da appiattire.)

Ecco la funzione corrispondente:

flatten = lambda l: [item for sublist in l for item in sublist]

Come prova, puoi usare il timeit modulo nella libreria standard:

$ python -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99' '[item for sublist in l for item in sublist]'
10000 loops, best of 3: 143 usec per loop
$ python -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99' 'sum(l, [])'
1000 loops, best of 3: 969 usec per loop
$ python -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99' 'reduce(lambda x,y: x+y,l)'
1000 loops, best of 3: 1.1 msec per loop

Spiegazione: le scorciatoie basate su +(incluso l'uso implicito insum ) sono, necessariamente, O(L**2)quando ci sono L elenchi secondari - poiché l'elenco dei risultati intermedi continua ad allungarsi, ad ogni passo viene assegnato un nuovo oggetto dell'elenco dei risultati intermedi e tutti gli elementi nel precedente risultato intermedio deve essere copiato (così come alcuni nuovi aggiunti alla fine). Quindi, per semplicità e senza perdita effettiva di generalità, supponiamo di avere L sotto-elenchi di oggetti I ciascuno: i primi oggetti I vengono copiati avanti e indietro L-1 volte, il secondo I oggetti L-2 volte e così via; il numero totale di copie è I volte la somma di x per x da 1 a L esclusa, ovvero I * (L**2)/2.

La comprensione dell'elenco genera solo un elenco, una volta, e copia ogni elemento (dal suo luogo di residenza originale all'elenco dei risultati) anche esattamente una volta.


486
Ho provato un test con gli stessi dati, utilizzando itertools.chain.from_iterable: $ python -mtimeit -s'from itertools import chain; l=[[1,2,3],[4,5,6], [7], [8,9]]*99' 'list(chain.from_iterable(l))'. Funziona un po 'più del doppio della comprensione dell'elenco nidificato che è la più veloce delle alternative mostrate qui.
intuito il

274
Ho trovato la sintassi difficile da capire fino a quando ho capito che puoi pensarla esattamente come nidificata per i loop. per la sotto-lista in l: per la voce in sotto-lista: rendimento
Rob Crowell

23
@BorisChervenkov: nota che ho inserito la chiamata list()per realizzare l'iteratore in un elenco.
intuito

163
[foglia per albero nella foresta per foglia nell'albero] potrebbe essere più facile da comprendere e applicare.
John Mee,

80
@Joel, in realtà al giorno d'oggi list(itertools.chain.from_iterable(l))è meglio - come notato in altri commenti e nella risposta di Shawn.
Alex Martelli,

1569

Puoi usare itertools.chain():

import itertools
list2d = [[1,2,3], [4,5,6], [7], [8,9]]
merged = list(itertools.chain(*list2d))

Oppure puoi usare itertools.chain.from_iterable()ciò che non richiede la decompressione dell'elenco con l' *operatore :

import itertools
list2d = [[1,2,3], [4,5,6], [7], [8,9]]
merged = list(itertools.chain.from_iterable(list2d))

13
La *è la cosa che rende difficile chainmeno semplice di quanto la lista di comprensione. Devi sapere che la catena unisce solo gli iterabili passati come parametri e * fa sì che l'elenco di livello superiore venga espanso in parametri, quindi chainunisce tutti quegli iterabili, ma non scende ulteriormente. Penso che questo renda la comprensione più leggibile dell'uso della catena in questo caso.
Tim Dierks,

52
@TimDierks: Non sono sicuro che "questo richiede di capire la sintassi di Python" è un argomento contro l'utilizzo di una determinata tecnica in Python. Certo, un uso complesso potrebbe confondere, ma l'operatore "splat" è generalmente utile in molte circostanze, e questo non lo sta usando in un modo particolarmente oscuro; rifiutare tutte le funzionalità linguistiche che non sono necessariamente ovvie per gli utenti principianti significa che ti stai legando una mano dietro la schiena. Può anche buttare via anche le comprensioni delle liste mentre ci sei; gli utenti di altri background troverebbero un forloop che è ripetutamente appendpiù ovvio.
ShadowRanger,

Questa risposta, e altre risposte qui, danno risultati errati se anche il livello superiore contiene un valore. per esempio, list = [["abc","bcd"],["cde","def"],"efg"]produrrà un output di["abc", "bcd", "cde", "def", "e", "f", "g"].
gouravkr il

Sembra che l' *operatore non possa essere usato in python2
wkm

908

Nota dell'autore : questo è inefficiente. Ma divertente, perché i monoidi sono fantastici. Non è appropriato per il codice Python di produzione.

>>> sum(l, [])
[1, 2, 3, 4, 5, 6, 7, 8, 9]

Questo somma semplicemente gli elementi di iterable passati nel primo argomento, trattando il secondo argomento come il valore iniziale della somma (se non fornito, 0 viene usato invece e questo caso ti darà un errore).

Poiché si sommano elenchi nidificati, in realtà si ottiene [1,3]+[2,4]come risultato di sum([[1,3],[2,4]],[]), che è uguale a[1,3,2,4] .

Si noti che funziona solo su elenchi di elenchi. Per gli elenchi di elenchi di elenchi, avrai bisogno di un'altra soluzione.


100
è abbastanza pulito e intelligente, ma non lo userei perché è confuso da leggere.
Andrewrk,

87
Questo è un algoritmo di Shlemiel il pittore joelonsoftware.com/articles/fog0000000319.html - inutilmente inefficiente e inutilmente brutto.
Mike Graham,

44
L'operazione di aggiunta negli elenchi forma a Monoid, che è una delle astrazioni più convenienti per pensare a +un'operazione in senso generale (non limitata ai soli numeri). Quindi questa risposta merita un mio +1 per il trattamento (corretto) delle liste come monoide. La performance riguarda però ...
Ulidtko,

7
@andrewrk Beh, alcune persone pensano che questo sia il modo più pulito di farlo: youtube.com/watch?v=IOiZatlZtGU quelli che non capiscono perché questo è bello devono solo aspettare qualche decennio finché tutti lo fanno in questo modo: ) usiamo i linguaggi di programmazione (e le astrazioni) che vengono scoperti e non inventati, viene scoperto Monoid.
jhegedus,

11
questo è un modo molto inefficiente a causa dell'aspetto quadratico della somma.
Jean-François Fabre

461

Ho testato la maggior parte delle soluzioni suggerite con perfplot (un mio progetto per animali domestici, essenzialmente un involucro in giro timeit) e ho trovato

functools.reduce(operator.iconcat, a, [])

essere la soluzione più veloce, sia quando sono concatenate molte piccole liste che poche liste lunghe. ( operator.iaddè altrettanto veloce.)

inserisci qui la descrizione dell'immagine

inserisci qui la descrizione dell'immagine


Codice per riprodurre la trama:

import functools
import itertools
import numpy
import operator
import perfplot


def forfor(a):
    return [item for sublist in a for item in sublist]


def sum_brackets(a):
    return sum(a, [])


def functools_reduce(a):
    return functools.reduce(operator.concat, a)


def functools_reduce_iconcat(a):
    return functools.reduce(operator.iconcat, a, [])


def itertools_chain(a):
    return list(itertools.chain.from_iterable(a))


def numpy_flat(a):
    return list(numpy.array(a).flat)


def numpy_concatenate(a):
    return list(numpy.concatenate(a))


perfplot.show(
    setup=lambda n: [list(range(10))] * n,
    # setup=lambda n: [list(range(n))] * 10,
    kernels=[
        forfor,
        sum_brackets,
        functools_reduce,
        functools_reduce_iconcat,
        itertools_chain,
        numpy_flat,
        numpy_concatenate,
    ],
    n_range=[2 ** k for k in range(16)],
    xlabel="num lists (of length 10)",
    # xlabel="len lists (10 lists total)"
)

25
Per enormi elenchi nidificati, 'list (numpy.array (a) .flat)' è il più veloce tra tutte le funzioni sopra.
Sara,

Ho provato usando regex: 'list (map (int, re.findall (r "[\ w] +", str (a))))'. La velocità è un po 'più lenta che numpy_concatenate
Justas il

C'è un modo per fare un perfplot 3D? numero di array per dimensione media dell'array?
Leo,

Adoro la tua soluzione. Breve, semplice ed efficiente :-)
ShadyMBA

181
from functools import reduce #python 3

>>> l = [[1,2,3],[4,5,6], [7], [8,9]]
>>> reduce(lambda x,y: x+y,l)
[1, 2, 3, 4, 5, 6, 7, 8, 9]

Il extend()metodo nell'esempio modifica xanziché restituire un valore utile (che reduce()prevede).

Un modo più veloce per fare la reduceversione sarebbe

>>> import operator
>>> l = [[1,2,3],[4,5,6], [7], [8,9]]
>>> reduce(operator.concat, l)
[1, 2, 3, 4, 5, 6, 7, 8, 9]

19
reduce(operator.add, l)sarebbe il modo corretto di fare la reduceversione. Gli built-in sono più veloci di lambda.
AGF

3
@agf ecco come: * timeit.timeit('reduce(operator.add, l)', 'import operator; l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]]', number=10000) 0.017956018447875977 * timeit.timeit('reduce(lambda x, y: x+y, l)', 'import operator; l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]]', number=10000) 0.025218963623046875
lukmdo

8
Questo è l'algoritmo di Shlemiel il pittore joelonsoftware.com/articles/fog0000000319.html
Mike Graham

2
questo può usare solo per integers. Ma cosa succede se l'elenco contiene string?
Freddy,

3
@Freddy: la operator.addfunzione funziona ugualmente bene sia per gli elenchi di numeri interi che per quelli di stringhe.
Greg Hewgill,

121

Non reinventare la ruota se usi Django :

>>> from django.contrib.admin.utils import flatten
>>> l = [[1,2,3], [4,5], [6]]
>>> flatten(l)
>>> [1, 2, 3, 4, 5, 6]

... Panda :

>>> from pandas.core.common import flatten
>>> list(flatten(l))

... Itertools :

>>> import itertools
>>> flatten = itertools.chain.from_iterable
>>> list(flatten(l))

... Matplotlib

>>> from matplotlib.cbook import flatten
>>> list(flatten(l))

... Unipath :

>>> from unipath.path import flatten
>>> list(flatten(l))

... Setuptools :

>>> from setuptools.namespaces import flatten
>>> list(flatten(l))

4
flatten = itertools.chain.from_iterabledovrebbe essere la risposta giusta
gechi il

3
Bella risposta! funziona anche per l = [[[1, 2, 3], [4, 5]], 5] nel caso dei panda
Markus Dutschke,

1
Mi piace la soluzione di Panda. Se si dispone di qualcosa come: list_of_menuitems = [1, 2, [3, [4, 5, [6]]]], il risultato sarà il: [1, 2, 3, 4, 5, 6]. Quello che mi manca è il livello piatto.
imjoseangel,

115

Ecco un approccio generale che si applica a numeri , stringhe , elenchi nidificati e contenitori misti .

Codice

#from typing import Iterable 
from collections import Iterable                            # < py38


def flatten(items):
    """Yield items from any nested iterable; see Reference."""
    for x in items:
        if isinstance(x, Iterable) and not isinstance(x, (str, bytes)):
            for sub_x in flatten(x):
                yield sub_x
        else:
            yield x

Note :

  • In Python 3, yield from flatten(x)può sostituirefor sub_x in flatten(x): yield sub_x
  • In Python 3.8, classi base astratte vengono spostati dal collection.abcal typingmodulo.

dimostrazione

lst = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
list(flatten(lst))                                         # nested lists
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

mixed = [[1, [2]], (3, 4, {5, 6}, 7), 8, "9"]              # numbers, strs, nested & mixed
list(flatten(mixed))
# [1, 2, 3, 4, 5, 6, 7, 8, '9']

Riferimento

  • Questa soluzione è stata modificata da una ricetta in Beazley, D. e B. Jones. Ricetta 4.14, Python Cookbook 3rd Ed., O'Reilly Media Inc. Sebastopol, CA: 2013.
  • Ho trovato un precedente post SO , forse la dimostrazione originale.

5
Ho appena scritto più o meno la stessa cosa, perché non ho visto la tua soluzione ... ecco cosa ho cercato "appiattisci in modo ricorsivo elenchi multipli completi" ... (+1)
Martin Thoma

3
@MartinThoma Molto apprezzato. Cordiali saluti, se appiattire iterabili nidificati è una pratica comune per te, ci sono alcuni pacchetti di terze parti che gestiscono bene. Questo può salvare dal reinventare la ruota. Ne ho parlato more_itertoolstra l'altro, discusso in questo post. Saluti.
pylang

Forse traversepotrebbe anche essere un buon nome per questo modo di un albero, mentre lo terrei meno universale per questa risposta attenendomi agli elenchi nidificati.
Lupo,

Puoi controllare if hasattr(x, '__iter__')invece di importare / verificare contro Iterablee questo escluderà anche le stringhe.
Ryan Allen,

il codice sopra sembra non funzionare se uno degli elenchi nidificati ha un elenco di stringhe. [1, 2, [3, 4], [4], [], 9, 9.5, 'ssssss', ['str', 'sss', 'ss'], [3, 4, 5]] output: - [1, 2, 3, 4, 4, 9, 9.5, 'ssssss', 3, 4, 5]
sunnyX

51

Se si desidera appiattire una struttura di dati in cui non si conosce la profondità con cui è nidificata, è possibile utilizzare 1iteration_utilities.deepflatten

>>> from iteration_utilities import deepflatten

>>> l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
>>> list(deepflatten(l, depth=1))
[1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> l = [[1, 2, 3], [4, [5, 6]], 7, [8, 9]]
>>> list(deepflatten(l))
[1, 2, 3, 4, 5, 6, 7, 8, 9]

È un generatore, quindi è necessario eseguire il cast del risultato listo eseguirne l'iterazione esplicita.


Per appiattire solo un livello e se ciascuno degli elementi è stesso iterabile, puoi anche usare iteration_utilities.flattenquale di per sé è solo un involucro sottile itertools.chain.from_iterable:

>>> from iteration_utilities import flatten
>>> l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
>>> list(flatten(l))
[1, 2, 3, 4, 5, 6, 7, 8, 9]

Solo per aggiungere alcuni tempi (basato sulla risposta di Nico Schlömer che non includeva la funzione presentata in questa risposta):

inserisci qui la descrizione dell'immagine

È un diagramma log-log per adattarsi alla vasta gamma di valori spanning. Per ragionamento qualitativo: Abbassare è meglio.

I risultati mostrano che se iterable contiene solo pochi iterables interne allora sumsarà più veloce, tuttavia per lunghi iterables solo le itertools.chain.from_iterable, iteration_utilities.deepflatteno la comprensione nidificata avere prestazioni ragionevoli con itertools.chain.from_iterablequella massima (come già notato da Nico Schlömer).

from itertools import chain
from functools import reduce
from collections import Iterable  # or from collections.abc import Iterable
import operator
from iteration_utilities import deepflatten

def nested_list_comprehension(lsts):
    return [item for sublist in lsts for item in sublist]

def itertools_chain_from_iterable(lsts):
    return list(chain.from_iterable(lsts))

def pythons_sum(lsts):
    return sum(lsts, [])

def reduce_add(lsts):
    return reduce(lambda x, y: x + y, lsts)

def pylangs_flatten(lsts):
    return list(flatten(lsts))

def flatten(items):
    """Yield items from any nested iterable; see REF."""
    for x in items:
        if isinstance(x, Iterable) and not isinstance(x, (str, bytes)):
            yield from flatten(x)
        else:
            yield x

def reduce_concat(lsts):
    return reduce(operator.concat, lsts)

def iteration_utilities_deepflatten(lsts):
    return list(deepflatten(lsts, depth=1))


from simple_benchmark import benchmark

b = benchmark(
    [nested_list_comprehension, itertools_chain_from_iterable, pythons_sum, reduce_add,
     pylangs_flatten, reduce_concat, iteration_utilities_deepflatten],
    arguments={2**i: [[0]*5]*(2**i) for i in range(1, 13)},
    argument_name='number of inner lists'
)

b.plot()

1 Disclaimer: sono l'autore di quella biblioteca


sumnon funziona più su sequenze arbitrarie all'inizio 0, effettuando functools.reduce(operator.add, sequences)la sostituzione (non siamo contenti che siano stati rimossi reducedai builtin?). Quando i tipi sono noti, potrebbe essere più veloce da usare type.__add__.
Yann Vernier

@YannVernier Grazie per l'informazione. Ho pensato di eseguire questi benchmark su Python 3.6 e ha funzionato sum. Ti capita di sapere su quali versioni di Python ha smesso di funzionare?
MSeifert,

Mi ero in qualche modo sbagliato. 0è solo il valore iniziale predefinito, quindi funziona se si utilizza l' argomento start per iniziare con un elenco vuoto ... ma contiene ancora stringhe di casi speciali e mi dice di usare join. Si sta implementando foldlinvece di foldl1. Lo stesso problema si presenta in 2.7.
Yann Vernier,

39

Riprendo la mia dichiarazione. la somma non è il vincitore. Anche se è più veloce quando l'elenco è piccolo. Ma le prestazioni peggiorano significativamente con elenchi più grandi.

>>> timeit.Timer(
        '[item for sublist in l for item in sublist]',
        'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]] * 10000'
    ).timeit(100)
2.0440959930419922

La versione somma è ancora in esecuzione per più di un minuto e non ha ancora elaborato!

Per elenchi medi:

>>> timeit.Timer(
        '[item for sublist in l for item in sublist]',
        'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]] * 10'
    ).timeit()
20.126545906066895
>>> timeit.Timer(
        'reduce(lambda x,y: x+y,l)',
        'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]] * 10'
    ).timeit()
22.242258071899414
>>> timeit.Timer(
        'sum(l, [])',
        'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]] * 10'
    ).timeit()
16.449732065200806

Utilizzo di piccoli elenchi e timeit: numero = 1000000

>>> timeit.Timer(
        '[item for sublist in l for item in sublist]',
        'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]]'
    ).timeit()
2.4598159790039062
>>> timeit.Timer(
        'reduce(lambda x,y: x+y,l)',
        'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]]'
    ).timeit()
1.5289170742034912
>>> timeit.Timer(
        'sum(l, [])',
        'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]]'
    ).timeit()
1.0598428249359131

23
per un elenco veramente minuscolo, ad esempio uno con 3 elenchi secondari, forse - ma dal momento che le prestazioni della somma vanno con O (N ** 2) mentre la comprensione dell'elenco va con O (N), solo un aumento dell'elenco di input invertirà leggermente le cose - - infatti la LC sarà "infinitamente più veloce" della somma al limite man mano che N cresce. Ero responsabile della progettazione della somma e della sua prima implementazione nel runtime di Python, e vorrei ancora aver trovato un modo per limitarlo efficacemente alla somma dei numeri (in cosa è veramente bravo) e bloccare la "seccatura attraente" che offre alle persone chi vuole "sommare" le liste ;-).
Alex Martelli,

38

Sembra esserci una confusione con operator.add! Quando aggiungi due elenchi insieme, il termine corretto è " concatnon aggiungere". operator.concatè quello che devi usare.

Se stai pensando funzionale, è facile come questo ::

>>> from functools import reduce
>>> list2d = ((1, 2, 3), (4, 5, 6), (7,), (8, 9))
>>> reduce(operator.concat, list2d)
(1, 2, 3, 4, 5, 6, 7, 8, 9)

Si vede ridurre rispetta il tipo di sequenza, quindi quando si fornisce una tupla, si ottiene una tupla. Proviamo con un elenco ::

>>> list2d = [[1, 2, 3],[4, 5, 6], [7], [8, 9]]
>>> reduce(operator.concat, list2d)
[1, 2, 3, 4, 5, 6, 7, 8, 9]

Ah, hai un elenco.

Che ne dici di prestazioni ::

>>> list2d = [[1, 2, 3],[4, 5, 6], [7], [8, 9]]
>>> %timeit list(itertools.chain.from_iterable(list2d))
1000000 loops, best of 3: 1.36 µs per loop

from_iterableè abbastanza veloce! Ma non è un confronto con cui ridurre concat.

>>> list2d = ((1, 2, 3),(4, 5, 6), (7,), (8, 9))
>>> %timeit reduce(operator.concat, list2d)
1000000 loops, best of 3: 492 ns per loop

1
Per essere onesti, il secondo esempio dovrebbe essere anche l'elenco (o la prima tupla?)
Mr_and_Mrs_D

2
L'uso di input così piccoli non è un paragone equo. Per 1000 sequenze di lunghezza 1000, ottengo 0,037 secondi per list(chain.from_iterable(...))e 2,5 secondi per reduce(concat, ...). Il problema è che reduce(concat, ...)ha un tempo di esecuzione quadratico, mentre chainè lineare.
kaya3

33

Perché usi extender?

reduce(lambda x, y: x+y, l)

Questo dovrebbe funzionare bene.


7
per python3from functools import reduce
andorov il

Scusa se è molto lento, vedi il resto delle risposte
Mr_and_Mrs_D,

Questa è di gran lunga la soluzione più semplice da capire ma breve che funziona su Python 2 e 3. Mi rendo conto che molte persone di Python sono nell'elaborazione dei dati in cui ci sono enormi quantità di dati da elaborare e quindi si preoccupano molto della velocità, ma quando si stanno scrivendo uno script di shell e hanno solo una dozzina di elementi in alcune liste secondarie, quindi questo è perfetto.
Asfand Qazi,

27

Prendi in considerazione l'installazione del more_itertoolspacchetto.

> pip install more_itertools

Viene fornito con un'implementazione per flatten( fonte , dalle ricette itertools ):

import more_itertools


lst = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
list(more_itertools.flatten(lst))
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

A partire dalla versione 2.4, puoi appiattire gli iterable più complicati e nidificati con more_itertools.collapse( fonte , fornita da Abarnet).

lst = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
list(more_itertools.collapse(lst)) 
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

lst = [[1, 2, 3], [[4, 5, 6]], [[[7]]], 8, 9]              # complex nesting
list(more_itertools.collapse(lst))
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

Infatti. Questa dovrebbe essere la risposta accettata
Brunetton,

Se puoi permetterti di aggiungere un pacchetto al tuo progetto - questa è la migliore risposta
viddik13

22

Il motivo per cui la funzione non ha funzionato è perché l' estensione estende un array sul posto e non lo restituisce. Puoi ancora restituire x da lambda, usando qualcosa del genere:

reduce(lambda x,y: x.extend(y) or x, l)

Nota: L'estensione è più efficiente di + nelle liste.


7
extendè meglio usato come newlist = [], extend = newlist.extend, for sublist in l: extend(l)in quanto evita la (piuttosto grande) di tutto il lambda, la ricerca attributo x, e or.
AGF

per python 3 aggiungifrom functools import reduce
Markus Dutschke il

17
def flatten(l, a):
    for i in l:
        if isinstance(i, list):
            flatten(i, a)
        else:
            a.append(i)
    return a

print(flatten([[[1, [1,1, [3, [4,5,]]]], 2, 3], [4, 5],6], []))

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

def flatten(l, a=None): if a is None: a = [][...]
Poik,

16

Versione ricorsiva

x = [1,2,[3,4],[5,[6,[7]]],8,9,[10]]

def flatten_list(k):
    result = list()
    for i in k:
        if isinstance(i,list):

            #The isinstance() function checks if the object (first argument) is an 
            #instance or subclass of classinfo class (second argument)

            result.extend(flatten_list(i)) #Recursive call
        else:
            result.append(i)
    return result

flatten_list(x)
#result = [1,2,3,4,5,6,7,8,9,10]

1
bello, non sono necessarie importazioni ed è chiaro cosa sta facendo ... appiattire un elenco, punto :)
Goran B.

1
semplicemente geniale!
Sachin Sharma,

15

matplotlib.cbook.flatten() funzionerà per gli elenchi nidificati anche se nidificano più profondamente dell'esempio.

import matplotlib
l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
print(list(matplotlib.cbook.flatten(l)))
l2 = [[1, 2, 3], [4, 5, 6], [7], [8, [9, 10, [11, 12, [13]]]]]
print list(matplotlib.cbook.flatten(l2))

Risultato:

[1, 2, 3, 4, 5, 6, 7, 8, 9]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]

Questo è 18 volte più veloce del trattino basso ._. Flatten:

Average time over 1000 trials of matplotlib.cbook.flatten: 2.55e-05 sec
Average time over 1000 trials of underscore._.flatten: 4.63e-04 sec
(time for underscore._)/(time for matplotlib.cbook) = 18.1233394636

14

La risposta accettata non ha funzionato per me quando ho a che fare con elenchi testuali di lunghezza variabile. Ecco un approccio alternativo che ha funzionato per me.

l = ['aaa', 'bb', 'cccccc', ['xx', 'yyyyyyy']]

Risposta accettata che non ha funzionato:

flat_list = [item for sublist in l for item in sublist]
print(flat_list)
['a', 'a', 'a', 'b', 'b', 'c', 'c', 'c', 'c', 'c', 'c', 'xx', 'yyyyyyy']

Nuova soluzione proposta che ha funzionato per me:

flat_list = []
_ = [flat_list.extend(item) if isinstance(item, list) else flat_list.append(item) for item in l if item]
print(flat_list)
['aaa', 'bb', 'cccccc', 'xx', 'yyyyyyy']

13

Una cattiva caratteristica della funzione di Anil sopra è che richiede all'utente di specificare sempre manualmente il secondo argomento in modo che sia un elenco vuoto [] . Questo dovrebbe invece essere un valore predefinito. A causa del modo in cui funzionano gli oggetti Python, questi dovrebbero essere impostati all'interno della funzione, non negli argomenti.

Ecco una funzione operativa:

def list_flatten(l, a=None):
    #check a
    if a is None:
        #initialize with empty list
        a = []

    for i in l:
        if isinstance(i, list):
            list_flatten(i, a)
        else:
            a.append(i)
    return a

test:

In [2]: lst = [1, 2, [3], [[4]],[5,[6]]]

In [3]: lst
Out[3]: [1, 2, [3], [[4]], [5, [6]]]

In [11]: list_flatten(lst)
Out[11]: [1, 2, 3, 4, 5, 6]

13

Di seguito mi sembra più semplice:

>>> import numpy as np
>>> l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
>>> print (np.concatenate(l))
[1 2 3 4 5 6 7 8 9]

Non funziona per elenchi con dimensioni diverse. -1
Nurub

10

Si può anche usare l' appartamento di NumPy :

import numpy as np
list(np.array(l).flat)

Modifica 11/02/2016: funziona solo quando le liste secondarie hanno dimensioni identiche.


sarebbe quella la soluzione ottimale?
RetroCode

6

Puoi usare numpy:
flat_list = list(np.concatenate(list_of_list))


Funziona anche con numeri, stringhe ed elenchi misti
Nitin,

2
Non riesce a ottenere dati nidificati in modo non uniforme, ad esempio[1, 2, [3], [[4]], [5, [6]]]
EL_DON

5

Se sei disposto a rinunciare a una piccola quantità di velocità per un aspetto più pulito, puoi utilizzare numpy.concatenate().tolist()o numpy.concatenate().ravel().tolist():

import numpy

l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]] * 99

%timeit numpy.concatenate(l).ravel().tolist()
1000 loops, best of 3: 313 µs per loop

%timeit numpy.concatenate(l).tolist()
1000 loops, best of 3: 312 µs per loop

%timeit [item for sublist in l for item in sublist]
1000 loops, best of 3: 31.5 µs per loop

Puoi saperne di più qui nei documenti numpy.concatenate e numpy.ravel


1
Non funziona per elenchi annidati in modo non uniforme come[1, 2, [3], [[4]], [5, [6]]]
EL_DON

5

La soluzione più veloce che ho trovato (comunque per una grande lista):

import numpy as np
#turn list into an array and flatten()
np.array(l).flatten()

Fatto! Ovviamente puoi trasformarlo in un elenco eseguendo l'elenco (l)


1
Questo è sbagliato, l'appiattimento ridurrà le dimensioni dell'array nd a una, ma non concatenerà le liste all'interno come una.
Ando Jurai,

5

Codice semplice per il underscore.pyfan del pacchetto

from underscore import _
_.flatten([[1, 2, 3], [4, 5, 6], [7], [8, 9]])
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

Risolve tutti i problemi di appiattimento (nessun elemento dell'elenco o nidificazione complessa)

from underscore import _
# 1 is none list item
# [2, [3]] is complex nesting
_.flatten([1, [2, [3]], [4, 5, 6], [7], [8, 9]])
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

È possibile installare underscore.pycon pip

pip install underscore.py

Allo stesso modo, puoi usare pydash . Trovo che questa versione sia molto più leggibile della comprensione dell'elenco o di qualsiasi altra risposta.
Gliemezis,

2
Questo è super lento.
Nico Schlömer,

2
Perché ha un modulo chiamato _? Sembra un brutto nome. Vedere stackoverflow.com/a/5893946/6605826
EL_DON

2
@EL_DON: dalla pagina readme underscore.py "Underscore.py è una porta Python di eccellente libreria javascript underscore.js". Penso che sia la ragione di questo nome. E sì, non è un buon nome per Python
Vu Anh,

5
def flatten(alist):
    if alist == []:
        return []
    elif type(alist) is not list:
        return [alist]
    else:
        return flatten(alist[0]) + flatten(alist[1:])

Non riesce per python2.7 per l'elenco di esempio nidificato nella domanda:[[1, 2, 3], [4, 5, 6], [7], [8, 9]]
EL_DON

@EL_DON testato su Python 2.7.5. funziona benissimo
englealuze il

5

Nota : di seguito si applica a Python 3.3+ perché utilizza yield_from. sixè anche un pacchetto di terze parti, sebbene sia stabile. In alternativa, potresti usare sys.version.


Nel caso di obj = [[1, 2,], [3, 4], [5, 6]], tutte le soluzioni qui sono buone, compresa la comprensione dell'elenco e itertools.chain.from_iterable.

Tuttavia, considera questo caso leggermente più complesso:

>>> obj = [[1, 2, 3], [4, 5], 6, 'abc', [7], [8, [9, 10]]]

Ci sono diversi problemi qui:

  • Un elemento, 6è solo uno scalare; non è iterabile, quindi i percorsi di cui sopra falliranno qui.
  • Un elemento, 'abc', è tecnicamente iterabile (tuttistr s sono). Tuttavia, leggendo un po 'tra le righe, non vuoi trattarlo come tale - vuoi trattarlo come un singolo elemento.
  • L'elemento finale, [8, [9, 10]]è esso stesso un iterabile annidato. Comprensione di base dell'elenco ed chain.from_iterableestratto solo "1 livello in basso".

Puoi rimediare come segue:

>>> from collections import Iterable
>>> from six import string_types

>>> def flatten(obj):
...     for i in obj:
...         if isinstance(i, Iterable) and not isinstance(i, string_types):
...             yield from flatten(i)
...         else:
...             yield i


>>> list(flatten(obj))
[1, 2, 3, 4, 5, 6, 'abc', 7, 8, 9, 10]

Qui, controlli che l'elemento secondario (1) sia iterabile con Iterable, un ABC da itertools, ma vuoi anche assicurarti che (2) l'elemento non sia "simile a una stringa".


1
Se sei ancora interessato alla compatibilità con Python 2, yield frompassa a un forloop, ad esempiofor x in flatten(i): yield x
pylang

5
flat_list = []
for i in list_of_list:
    flat_list+=i

Questo codice funziona anche bene in quanto estende l'elenco fino in fondo. Anche se è molto simile ma ne ha solo uno per loop. Quindi ha meno complessità rispetto all'aggiunta di 2 per i loop.


5
from nltk import flatten

l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
flatten(l)

Il vantaggio di questa soluzione rispetto alla maggior parte degli altri qui è che se hai un elenco come:

l = [1, [2, 3], [4, 5, 6], [7], [8, 9]]

mentre la maggior parte delle altre soluzioni genera un errore, questa soluzione li gestisce.


La domanda indica un "elenco di elenchi", ma l'elenco di esempio include un elemento non di elenco. La maggior parte delle altre soluzioni si attengono alla domanda originale. La tua soluzione risolve un problema più ampio, ma richiede anche un pacchetto Python non base (nltk) che deve essere installato per primo.
simonobo

4

Questo potrebbe non essere il modo più efficiente, ma ho pensato di mettere un liner (in realtà un liner doppio). Entrambe le versioni funzionano su elenchi nidificati di gerarchie arbitrarie e sfruttano le funzionalità del linguaggio (Python3.5) e la ricorsione.

def make_list_flat (l):
    flist = []
    flist.extend ([l]) if (type (l) is not list) else [flist.extend (make_list_flat (e)) for e in l]
    return flist

a = [[1, 2], [[[[3, 4, 5], 6]]], 7, [8, [9, [10, 11], 12, [13, 14, [15, [[16, 17], 18]]]]]]
flist = make_list_flat(a)
print (flist)

L'output è

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]

Questo funziona in modo approfondito in primo luogo. La ricorsione scende fino a quando non trova un elemento non di elenco, quindi estende la variabile locale fliste quindi la ripristina al genitore. Ogni volta che flistviene restituito, viene esteso a quello dei genitori flistnella comprensione dell'elenco. Pertanto, alla radice, viene restituito un elenco semplice.

Quello sopra crea diversi elenchi locali e li restituisce che vengono utilizzati per estendere l'elenco dei genitori. Penso che il modo per aggirare questo potrebbe essere la creazione di un gloabl flist, come di seguito.

a = [[1, 2], [[[[3, 4, 5], 6]]], 7, [8, [9, [10, 11], 12, [13, 14, [15, [[16, 17], 18]]]]]]
flist = []
def make_list_flat (l):
    flist.extend ([l]) if (type (l) is not list) else [make_list_flat (e) for e in l]

make_list_flat(a)
print (flist)

L'output è di nuovo

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]

Anche se in questo momento non sono sicuro dell'efficienza.


Perché estendere ([l]) invece di append (l)?
Maciek,

3

Un altro approccio insolito che funziona per elenchi eterogenei e omogenei di numeri interi:

from typing import List


def flatten(l: list) -> List[int]:
    """Flatten an arbitrary deep nested list of lists of integers.

    Examples:
        >>> flatten([1, 2, [1, [10]]])
        [1, 2, 1, 10]

    Args:
        l: Union[l, Union[int, List[int]]

    Returns:
        Flatted list of integer
    """
    return [int(i.strip('[ ]')) for i in str(l).split(',')]

Questo è solo un modo più complicato e un po 'più lento di ciò che ᴡʜᴀᴄᴋᴀᴍᴀᴅᴏᴏᴅʟᴇ3000 ha già pubblicato prima. Ieri ho reinventato la sua proposta, quindi questo approccio sembra abbastanza popolare in questi giorni;)
Darkonaut il

Non proprio: wierd_list = [[1, 2, 3], [4, 5, 6], [7], [8, 9], 10]>>nice_list=[1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 0]
tharndt l'

il mio codice come flat_list = [int(e.replace('[','').replace(']','')) for e in str(deep_list).split(',')]
copertina

1
Hai proprio ragione +1, la proposta di 0003000 non funzionerà con numeri a più cifre, anche io non l'ho mai provato prima, anche se dovrebbe essere ovvio. Potresti semplificare il tuo codice e scrivere [int(e.strip('[ ]')) for e in str(deep_list).split(',')]. Ma suggerirei di attenersi alla proposta di Deleet per casi d'uso reali. Non contiene trasformazioni di tipo hacky, è più veloce e più versatile perché gestisce naturalmente anche elenchi con tipi misti.
Darkonaut,

2
Sfortunatamente no. Ma di recente ho visto questo codice qui: Python Practice Book 6.1.2
tharndt,
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.