Me pregunto si hay un atajo para hacer una lista simple de una lista de listas en Python.
Puedo hacer eso en un bucle for
, pero ¿tal vez hay algún "one-liner" genial? Lo probé con reduce()
, pero recibo un error.
Código
l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
reduce(lambda x, y: x.extend(y), l)
Mensaje de error
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 1, in <lambda>
AttributeError: 'NoneType' object has no attribute 'extend'
29 respuestas
Dada una lista de listas l
,
flat_list = [item for sublist in l for item in sublist]
Lo que significa:
flat_list = []
for sublist in l:
for item in sublist:
flat_list.append(item)
Es más rápido que los accesos directos publicados hasta ahora. (l
es la lista para aplanar).
Aquí está la función correspondiente:
flatten = lambda l: [item for sublist in l for item in sublist]
Como evidencia, puede usar el módulo timeit
en la biblioteca estándar:
$ 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
Explicación: los accesos directos basados en +
(incluido el uso implícito en sum
) son, necesariamente, O(L**2)
cuando hay sublistas L, ya que la lista de resultados intermedios se alarga cada vez más en cada paso se asigna un nuevo objeto de la lista de resultados intermedios y se deben copiar todos los elementos del resultado intermedio anterior (así como algunos nuevos agregados al final). Entonces, por simplicidad y sin pérdida real de generalidad, digamos que tiene L sublistas de ítems I cada uno: los primeros ítems I se copian una y otra vez L-1 veces, los segundos I ítems L-2 veces, y así sucesivamente; el número total de copias es I multiplicado por la suma de x para x de 1 a L excluido, es decir, I * (L**2)/2
.
La comprensión de la lista solo genera una lista, una vez, y copia cada elemento (desde su lugar de residencia original a la lista de resultados) también exactamente una vez.
La razón por la que su función no funcionó es porque extender extiende una matriz in situ y no la devuelve. Todavía puede devolver x de lambda, usando algo como esto:
reduce(lambda x,y: x.extend(y) or x, l)
Nota: extender es más eficiente que + en las listas.
Versión recursiva
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]
matplotlib.cbook.flatten()
funcionará para listas anidadas incluso si anidan más profundamente que en el ejemplo.
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))
Resultado:
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
Esto es 18 veces más rápido que el subrayado ._. 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
Considere instalar el paquete more_itertools
.
> pip install more_itertools
Se envía con una implementación para flatten
(fuente, de recetas de 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 partir de la versión 2.4, puede aplanar iterables anidados más complicados con { {X0}} ( fuente , aportado por 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]
Retiro mi declaración. sum no es el ganador. Aunque es más rápido cuando la lista es pequeña. Pero el rendimiento se degrada significativamente con listas más grandes.
>>> 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 versión de suma todavía se está ejecutando durante más de un minuto y aún no se ha procesado!
Para listas medianas:
>>> 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
Usando pequeñas listas y timeit: number = 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
¿Por qué usas extender?
reduce(lambda x, y: x+y, l)
Esto debería funcionar bien.
Probé la mayoría de las soluciones sugeridas con perfplot (un proyecto favorito mío, esencialmente un envoltorio alrededor de timeit
), y encontrado
functools.reduce(operator.iconcat, a, [])
Ser la solución más rápida. (operator.iadd
es igual de rápido).
Código para reproducir 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,
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'
)
Nota del autor : Esto es ineficiente. Pero divertido, porque monoides son increíbles. No es apropiado para la producción de código Python.
>>> sum(l, [])
[1, 2, 3, 4, 5, 6, 7, 8, 9]
Esto solo suma los elementos de iterable pasados en el primer argumento, tratando el segundo argumento como el valor inicial de la suma (si no se proporciona, se usa 0
en su lugar y este caso le dará un error).
Debido a que está sumando listas anidadas, en realidad obtiene [1,3]+[2,4]
como 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 las listas de listas de listas, necesitará otra solución.
También se puede usar flat de NumPy:
import numpy as np
list(np.array(l).flat)
Edición 11/02/2016: solo funciona cuando las sublistas tienen dimensiones idénticas.
Lo siguiente me parece más simple:
>>> 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]
Si está dispuesto a renunciar a una pequeña cantidad de velocidad para una apariencia más limpia, entonces puede usar 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
Puede encontrar más información aquí en los documentos numpy.concatenate y numpy.ravel
¡Parece haber una confusión con operator.add
! Cuando agrega dos listas juntas, el término correcto para eso es concat
, no agregar. operator.concat
es lo que necesitas usar.
Si está pensando en funcional, es tan fácil como esto ::
>>> 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)
Verá reducir respeta el tipo de secuencia, por lo que cuando proporciona una tupla, obtiene una tupla. Probemos con una lista ::
>>> list2d = [[1, 2, 3],[4, 5, 6], [7], [8, 9]]
>>> reduce(operator.concat, list2d)
[1, 2, 3, 4, 5, 6, 7, 8, 9]
Ajá, recuperas una lista.
¿Qué hay de rendimiento ::
>>> 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
es bastante rápido! Pero no es una comparación reducir con 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
from nltk import flatten
l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
flatten(l)
La ventaja de esta solución sobre la mayoría de las demás aquí es que si tiene una lista como:
l = [1, [2, 3], [4, 5, 6], [7], [8, 9]]
Mientras que la mayoría de las otras soluciones arrojan un error, esta solución las maneja.
Puede que esta no sea la forma más eficiente, pero pensé en poner una línea (en realidad una de dos líneas). Ambas versiones funcionarán en listas anidadas de jerarquía arbitraria y explotan las características del lenguaje (Python3.5) y la recursividad.
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)
La salida es
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]
Esto funciona en profundidad de primera manera. La recursividad disminuye hasta que encuentra un elemento que no es de la lista, luego extiende la variable local flist
y luego la revierte al padre. Cada vez que se devuelve flist
, se extiende al flist
del padre en la comprensión de la lista. Por lo tanto, en la raíz, se devuelve una lista plana.
El anterior crea varias listas locales y las devuelve, que se utilizan para ampliar la lista de los padres. Creo que la solución para esto puede ser crear un gloabl flist
, como a continuación.
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)
La salida es de nuevo
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]
Aunque no estoy seguro en este momento sobre la eficiencia.
Otra forma divertida de hacer esto:
from functools import reduce
from operator import add
li=[[1,2],[3,4]]
x= reduce(add, li)
def flatten(alist):
if alist == []:
return []
elif type(alist) is not list:
return [alist]
else:
return flatten(alist[0]) + flatten(alist[1:])
Puedes usar numpy:
flat_list = list(np.concatenate(list_of_list))
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]
Puede usar itertools.chain()
:
>>> import itertools
>>> list2d = [[1,2,3], [4,5,6], [7], [8,9]]
>>> merged = list(itertools.chain(*list2d))
O puede usar itertools.chain.from_iterable()
que no requiere desempaquetar la lista con el operador *
:
>>> import itertools
>>> list2d = [[1,2,3], [4,5,6], [7], [8,9]]
>>> merged = list(itertools.chain.from_iterable(list2d))
Este enfoque es posiblemente más legible que [item for sublist in l for item in sublist]
y parece ser también 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
La solución más rápida que he encontrado (para una lista grande de todos modos):
import numpy as np
#turn list into an array and flatten()
np.array(l).flatten()
¡Hecho! Por supuesto, puede volver a convertirlo en una lista ejecutando list (l)
La respuesta aceptada no funcionó para mí cuando trataba con listas basadas en texto de longitudes variables. Aquí hay un enfoque alternativo que funcionó para mí.
l = ['aaa', 'bb', 'cccccc', ['xx', 'yyyyyyy']]
Respuesta aceptada que no funcionó:
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']
Nueva solución propuesta que funcionó para mí:
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']
Una mala característica de la función anterior de Anil es que requiere que el usuario especifique siempre manualmente el segundo argumento para que sea una lista vacía []
. Esto debería ser un valor predeterminado. Debido a la forma en que funcionan los objetos de Python, estos deben establecerse dentro de la función, no en los argumentos.
Aquí hay una función de trabajo:
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
Pruebas:
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]
No reinvente la rueda si está utilizando Django :
>>> from django.contrib.admin.utils import flatten
>>> l = [[1,2,3], [4,5], [6]]
>>> flatten(l)
>>> [1, 2, 3, 4, 5, 6]
... Pandas :
>>> 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))
... Herramientas de configuración :
>>> from setuptools.namespaces import flatten
>>> list(flatten(l))
Si desea aplanar una estructura de datos donde no sabe qué tan profundo está anidado, puede usar iteration_utilities.deepflatten
1
>>> 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]
Es un generador, por lo que debe convertir el resultado en un list
o iterarlo explícitamente.
Para aplanar solo un nivel y si cada uno de los elementos es iterable, también puede usar iteration_utilities.flatten
, que en sí mismo es una envoltura delgada alrededor de 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 para agregar algunos tiempos (basados en la respuesta de Nico Schlömer que no incluyó la función presentada en esta respuesta):
Es un diagrama log-log para acomodar la gran variedad de valores abarcados. Para razonamiento cualitativo: más bajo es mejor.
Los resultados muestran que si el iterable contiene solo unos pocos iterables internos, entonces sum
será más rápido, sin embargo, para iterables largos solo el itertools.chain.from_iterable
, iteration_utilities.deepflatten
o la comprensión anidada tienen un rendimiento razonable con {{ X3}} es el más rápido (como ya notó 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 Descargo de responsabilidad: Soy la autora de esa biblioteca
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]
El método extend()
en su ejemplo modifica x
en lugar de devolver un valor útil (que reduce()
espera).
Una forma más rápida de hacer la versión reduce
sería
>>> 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]
Código simple para underscore.py
paquete del ventilador
from underscore import _
_.flatten([[1, 2, 3], [4, 5, 6], [7], [8, 9]])
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
Resuelve todos los problemas de aplanamiento (ninguno de los elementos de la lista o anidamiento complejo)
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]
Puede instalar underscore.py
con pip
pip install underscore.py
flat_list = []
for i in list_of_list:
flat_list+=i
Este código también funciona bien, ya que solo extiende la lista por completo. Aunque es muy similar, solo tiene uno para el bucle. Por lo tanto, tiene menos complejidad que agregar 2 para bucles.
Otro enfoque inusual que funciona para listas heterogéneas y homogéneas de enteros:
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(',')]
Preguntas relacionadas
Nuevas preguntas
python
Python es un lenguaje de programación multipropósito, de tipificación dinámica y de múltiples paradigmas. Está diseñado para ser rápido de aprender, comprender y usar, y hacer cumplir una sintaxis limpia y uniforme. Tenga en cuenta que Python 2 está oficialmente fuera de soporte a partir del 01-01-2020. Aún así, para preguntas de Python específicas de la versión, agregue la etiqueta [python-2.7] o [python-3.x]. Cuando utilice una variante de Python (por ejemplo, Jython, PyPy) o una biblioteca (por ejemplo, Pandas y NumPy), inclúyala en las etiquetas.