¿Cómo hago una lista plana a partir de una lista de listas?
Tengo una lista de listas como
[
[1, 2, 3],
[4, 5, 6],
[7],
[8, 9]
]
¿Cómo puedo aplanarlo para conseguirlo [1, 2, 3, 4, 5, 6, 7, 8, 9]
?
Si su lista de listas proviene de una lista por comprensión anidada, el problema se puede resolver de manera más simple/directa arreglando la comprensión; consulte ¿Cómo puedo obtener un resultado plano a partir de una lista por comprensión en lugar de una lista anidada? .
Las soluciones más populares aquí generalmente solo aplanan un "nivel" de la lista anidada. Consulte Aplanar una lista de listas irregulares (anidadas arbitrariamente) para encontrar soluciones que aplanan completamente una estructura profundamente anidada (de forma recursiva, en general).
Una lista de listas nombradas xss
se puede aplanar usando una lista por comprensión :
flat_list = [
x
for xs in xss
for x in xs
]
Lo anterior equivale a:
flat_list = []
for xs in xss:
for x in xs:
flat_list.append(x)
Aquí está la función correspondiente:
def flatten(xss):
return [x for xs in xss for x in xs]
Este es el método más rápido. Como evidencia, usando el timeit
módulo en la biblioteca estándar, vemos:
$ python -mtimeit -s'xss=[[1,2,3],[4,5,6],[7],[8,9]]*99' '[x for xs in xss for x in xs]'
10000 loops, best of 3: 143 usec per loop
$ python -mtimeit -s'xss=[[1,2,3],[4,5,6],[7],[8,9]]*99' 'sum(xss, [])'
1000 loops, best of 3: 969 usec per loop
$ python -mtimeit -s'xss=[[1,2,3],[4,5,6],[7],[8,9]]*99' 'reduce(lambda xs, ys: xs + ys, xss)'
1000 loops, best of 3: 1.1 msec per loop
Explicación: los métodos basados en +
(incluido el uso implícito en sum
) son, necesariamente, O(L**2)
cuando hay L sublistas: a medida que la lista de resultados intermedios se hace más larga, en cada paso se asigna un nuevo objeto de lista de resultados intermedios y todos los elementos en el resultado intermedio anterior se deben copiar (así como algunos nuevos agregados al final). Entonces, por simplicidad y sin pérdida real de generalidad, digamos que tiene L sublistas de M elementos cada una: los primeros M elementos se copian de ida y vuelta L-1
, los segundos M elementos L-2
, y así sucesivamente; El número total de copias es M multiplicado por la suma de x para x de 1 a L excluido, es decir, M * (L**2)/2
.
La comprensión de la lista simplemente genera una lista, una vez, y copia cada elemento (desde su lugar de residencia original hasta la lista de resultados) también exactamente una vez.
Puedes usar itertools.chain()
:
>>> import itertools
>>> list2d = [[1,2,3], [4,5,6], [7], [8,9]]
>>> merged = list(itertools.chain(*list2d))
O puedes usar itertools.chain.from_iterable()
el cual no requiere descomprimir la lista con el *
operador:
>>> import itertools
>>> list2d = [[1,2,3], [4,5,6], [7], [8,9]]
>>> merged = list(itertools.chain.from_iterable(list2d))
Podría decirse que este enfoque es más legible [item for sublist in l for item in sublist]
y también parece ser más rápido:
$ python3 -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99;import itertools' 'list(itertools.chain.from_iterable(l))'
20000 loops, best of 5: 10.8 usec per loop
$ python3 -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 5: 21.7 usec per loop
$ python3 -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99' 'sum(l, [])'
1000 loops, best of 5: 258 usec per loop
$ python3 -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99;from functools import reduce' 'reduce(lambda x,y: x+y,l)'
1000 loops, best of 5: 292 usec per loop
$ python3 --version
Python 3.7.5rc1
Nota del autor : Esto es muy ineficiente. Pero divertido, porque los monoides son increíbles.
>>> xss = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
>>> sum(xss, [])
[1, 2, 3, 4, 5, 6, 7, 8, 9]
sum
suma los elementos del iterable xss
y utiliza el segundo argumento como valor inicial []
para la suma. (El valor inicial predeterminado es 0
, que no es una lista).
Debido a que estás sumando listas anidadas, en realidad obtienes [1,3]+[2,4]
un resultado de sum([[1,3],[2,4]],[])
, que es igual a [1,3,2,4]
.
Tenga en cuenta que solo funciona en listas de listas. Para listas de listas de listas, necesitará otra solución.
Probé la mayoría de las soluciones sugeridas con perfplot (uno de mis proyectos favoritos, esencialmente un contenedor timeit
) y encontré
import functools
import operator
functools.reduce(operator.iconcat, a, [])
para ser la solución más rápida, tanto cuando se concatenan muchas listas pequeñas como pocas listas largas. ( operator.iadd
es igualmente rápido.)
Una variante más simple y también aceptable es
out = []
for sublist in a:
out.extend(sublist)
Si el número de sublistas es grande, esto funciona un poco peor que la sugerencia anterior.
Código para reproducir la trama:
import functools
import itertools
import operator
import numpy as np
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(np.array(a).flat)
def numpy_concatenate(a):
return list(np.concatenate(a))
def extend(a):
out = []
for sublist in a:
out.extend(sublist)
return out
b = perfplot.bench(
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,
extend,
],
n_range=[2 ** k for k in range(16)],
xlabel="num lists (of length 10)",
# xlabel="len lists (10 lists total)"
)
b.save("out.png")
b.show()
Usando functools.reduce
, que agrega una lista acumulada xs
a la siguiente lista ys
:
from functools import reduce
xss = [[1,2,3], [4,5,6], [7], [8,9]]
out = reduce(lambda xs, ys: xs + ys, xss)
Producción:
[1, 2, 3, 4, 5, 6, 7, 8, 9]
Una forma más rápida usando operator.concat
:
from functools import reduce
import operator
xss = [[1,2,3], [4,5,6], [7], [8,9]]
out = reduce(operator.concat, xss)
Producción:
[1, 2, 3, 4, 5, 6, 7, 8, 9]
A continuación se presenta un enfoque general que se aplica a números , cadenas , listas anidadas y contenedores mixtos . Esto puede aplanar contenedores tanto simples como complicados (consulte también Demostración ).
Código
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
Notas :
- En Python 3,
yield from flatten(x)
puede reemplazarfor sub_x in flatten(x): yield sub_x
- En Python 3.8, las clases base abstractas se mueven del
collection.abc
módulotyping
.
Manifestación
simple = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
list(flatten(simple))
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
complicated = [[1, [2]], (3, 4, {5, 6}, 7), 8, "9"] # numbers, strs, nested & mixed
list(flatten(complicated))
# [1, 2, 3, 4, 5, 6, 7, 8, '9']
Referencia
- Esta solución es una modificación de una receta de Beazley, D. y B. Jones. Receta 4.14, Libro de cocina de Python, tercera edición, O'Reilly Media Inc. Sebastopol, CA: 2013.
- Encontré una publicación SO anterior , posiblemente la demostración original.