Skip to content

Come appiattire un elenco di liste in Python

Python

Questo articolo descrive come appiattare un elenco di elenchi (elenco nidificato) in Python. Puoi usare itertools.chain.from_iterable(), sum() e list comprehensions.

  • Appiattisci un elenco di elenchi (elenco 2D)
    • Appiattisci l’elenco con itertools.chain.from_iterable()
    • Appiattisci elenco con sum()
    • Appiattisci l’elenco con le comprensioni degli elenchi
    • Confronto di velocità
  • Appiattisci elenchi 3D e più multidimensionali ed elenchi irregolari

Usa ravel() o flatten() per applicare un array NumPy ndarray.

Al, vedere il seguente articolo su come contrario un ndarray o un elenco unidimensionale in due dimensioni.

Appiattisci un elenco di elenchi (elenco 2D)

Appiattisci l’elenco con itertools.chain.from_iterable()

Puoi appiattare un elenco di elenchi con itertools.chain.from_iterable().

import itertools

l_2d = [[0, 1], [2, 3]]

print(list(itertools.chain.from_iterable(l_2d)))
# [0, 1, 2, 3]

itertools.chain.from_iterable() richiede un iteratore, quindi se vuoi convertirlo in un elenco, usa list(). Non è necessario creare un elenco quando lo si utilizza in un’istruzione per.

Una tupla di tuple può essere gestita allo stesso modo. Nell’esempio seguente, il risultato viene convertito in una tupla con tuple(). Se hai bisogno di una lista, usa list().

t_2d = ((0, 1), (2, 3))

print(tuple(itertools.chain.from_iterable(t_2d)))
# (0, 1, 2, 3)

Solo gli elenchi 2D possono essere appiattiti con itertools.chain.from_iterable(). Nel caso di liste 3D o più multidimensionali, il risultato è il seguente.

l_3d = [[[0, 1], [2, 3]], [[4, 5], [6, 7]]]

print(list(itertools.chain.from_iterable(l_3d)))
# [[0, 1], [2, 3], [4, 5], [6, 7]]

Viene generato un errore se contiene oggetti non iterabili come int o float.

l_mix = [[0, 1], [2, 3], 4]

# print(list(itertools.chain.from_iterable(l_mix)))
# TypeError: 'int' object is not iterable

I casi con elenchi 3D o più multidimensionali ed elenchi irregolari sono descritti in seguito.

Appiattisci elenco con sum()

Puoi anche applicare un elenco di elenchi con la funzione incorporata sum().

È possibile specificare un valore iniziale come secondo argomento di sum(). Se si passa l’elenco vuoto [], l’operazione + dell’elenco concatenerà gli elenchi.

Si noti che il valore predefinito del secondo argomento è 0, quindi se omesso, viene generato un errore a causa dell’operazione + con int e list.

print(sum(l_2d, []))
# [0, 1, 2, 3]

# print(sum(l_2d))
# TypeError: unsupported operand type(s) for +: 'int' and 'list'

Le tuple possono essere gestite allo stesso modo.

print(sum(t_2d, ()))
# (0, 1, 2, 3)

Come itertools.chain.from_iterable(), non funziona per più di elenchi 3D o irregolari.

print(sum(l_3d, []))
# [[0, 1], [2, 3], [4, 5], [6, 7]]

# print(sum(l_mix, []))
# TypeError: can only concatenate list (not "int") to list

Appiattisci l’elenco con le comprensioni degli elenchi

Puoi anche appiattare un elenco di elenchi con comprensioni di elenchi nidificati.

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

flat = [x for row in matrix for x in row]
print(flat)
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

L’esempio sopra è equivalente al seguente ciclo per annidato.

flat = []
for row in matrix:
    for x in row:
        flat.append(x)

print(flat)
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

Nel caso della comprensione dell’elenco sopra, come con gli altri metodi, è possibile appattire solo un livello e viene generato un errore se vengono inclusi oggetti non iterabili.

È anche possibile rendere il nesting più profondo per supportare più di tre dimensioni, oppure fare ramificazioni condizionali a seconda della tipologia dell’elemento, ma questo è sconsigliato perché sarebbe troppo complicato.

Vedere l’articolo seguente per ulteriori informazioni sulla comprensione degli elenchi.

Confronto di velocità

Nota che sebbene() sia facile da usare, è molto più lento di itertools.chain.from_iterable() o di list comprehensions quando il numero di righe (il numero di liste interne) è grande. È meglio non utilizzare sum() quando il numero di righe è elevato e la velocità di elaborazione e l’efficienza della memoria sono importanti.

è necessario importare itertools, itertools.chain.from_iterable() è più veloce della comprensione dell’elenco.

Il seguente codice viene misurato utilizzando il comando magico %%timeit su Jupyter Notebook. Nota che non funziona su script Python.

5 righe:

l_2d_5 = [[0, 1, 2] for i in range(5)]
print(l_2d_5)
# [[0, 1, 2], [0, 1, 2], [0, 1, 2], [0, 1, 2], [0, 1, 2]]

%%timeit
list(itertools.chain.from_iterable(l_2d_5))
# 537 ns ± 4.59 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

%%timeit
sum(l_2d_5, [])
# 319 ns ± 1.85 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

%%timeit
[x for row in l_2d_5 for x in row]
# 764 ns ± 32.6 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

100 righe:

l_2d_100 = [[0, 1, 2] for i in range(100)]

%%timeit
list(itertools.chain.from_iterable(l_2d_100))
# 6.94 µs ± 139 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

%%timeit
sum(l_2d_100, [])
# 35.5 µs ± 1.2 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

%%timeit
[x for row in l_2d_100 for x in row]
# 13.5 µs ± 959 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

10000 righe:

l_2d_10000 = [[0, 1, 2] for i in range(10000)]

%%timeit
list(itertools.chain.from_iterable(l_2d_10000))
# 552 µs ± 79.4 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

%%timeit
sum(l_2d_10000, [])
# 343 ms ± 2.19 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%%timeit
[x for row in l_2d_10000 for x in row]
# 1.11 ms ± 110 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Appiattisci elenchi 3D e più multidimensionali ed elenchi irregolari

È necessario definire una nuova funzione per appiattare liste 3D e più multidimensionali o liste irregolari.

Il codice di esempio seguente si basa sul seguente articolo.

import collections

def flatten(l):
    for el in l:
        if isinstance(el, collections.abc.Iterable) and not isinstance(el, (str, bytes)):
            yield from flatten(el)
        else:
            yield el

Il tipo di elemento el viene controllato da isinstance() ed elaborato in modo ricorsivo.

Determina se el è iterabile da collections.abc.Iterable. È necessario importare le raccolte di librerie standard.

Anche la stringa str e la stringa byte byte sono iterabili, quindi sono esclusi. Se non escluso, sarà separato per ogni carattere.

Questa funzione può essere utilizzata in tutti i casi.

print(list(flatten(l_2d)))
# [0, 1, 2, 3]

print(list(flatten(l_3d)))
# [0, 1, 2, 3, 4, 5, 6, 7]

print(list(flatten(l_mix)))
# [0, 1, 2, 3, 4]

Non importa se sono inclusi vari oggetti iterabili come elenchi, tuple e intervallo.

l_t_r_mix = [[0, 1], (2, 3), 4, range(5, 8)]

print(list(flatten(l_t_r_mix)))
# [0, 1, 2, 3, 4, 5, 6, 7]

Se vuoi gestire solo l’elenco, non è necessario importare le raccolte. Tuple e intervallo non sono appiattiti, ma questo sarà sufficiente nella maggior parte dei casi.

def flatten_list(l):
    for el in l:
        if isinstance(el, list):
            yield from flatten_list(el)
        else:
            yield el

print(list(flatten_list(l_2d)))
# [0, 1, 2, 3]

print(list(flatten_list(l_3d)))
# [0, 1, 2, 3, 4, 5, 6, 7]

print(list(flatten_list(l_mix)))
# [0, 1, 2, 3, 4]

print(list(flatten_list(l_t_r_mix)))
# [0, 1, (2, 3), 4, range(5, 8)]

Puoi specificare più tipi per tupla nel secondo argomento di istanza().

def flatten_list_tuple_range(l):
    for el in l:
        if isinstance(el, (list, tuple, range)):
            yield from flatten_list_tuple_range(el)
        else:
            yield el

print(list(flatten_list_tuple_range(l_t_r_mix)))
# [0, 1, 2, 3, 4, 5, 6, 7]