¿Cómo hacer una lista plana de la lista de listas?

Me pregunto si hay un atajo para hacer una lista simple de listas en Python.

Puedo hacer eso en un bucle for, pero ¿quizás hay algún “one-liner” genial? Lo intenté con reducir , pero me sale 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 "", line 1, in  File "", line 1, in  AttributeError: 'NoneType' object has no attribute 'extend' 

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 la sum ) son, por necesidad, O(L**2) cuando hay L sublistas, ya que la lista de resultados intermedios se hace más larga, en cada paso un nuevo resultado intermedio la lista de objetos se asigna, y todos los elementos del resultado intermedio anterior deben copiarse (así como algunos nuevos agregados al final). Entonces, por simplicidad y sin pérdida real de generalidad, digamos que tiene L listas de I elementos cada uno: los primeros elementos I se copian una y otra vez L-1 veces, los elementos I segundo L-2 veces, y así sucesivamente; el número total de copias es I veces la sum de x para x de 1 a L excluidas, es decir, I * (L**2)/2 .

La lista de comprensión solo genera una lista, una vez, y copia cada elemento (de su lugar de residencia original a la lista de resultados) una sola vez.

Puedes usar itertools.chain() :

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

o, en Python> = 2.6, use itertools.chain.from_iterable() que no requiere desempaquetar la lista:

 >>> 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 también parece ser más rápido:

 [me@home]$ python -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99;import itertools' 'list(itertools.chain.from_iterable(l))' 10000 loops, best of 3: 24.2 usec per loop [me@home]$ 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: 45.2 usec per loop [me@home]$ python -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99' 'sum(l, [])' 1000 loops, best of 3: 488 usec per loop [me@home]$ 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: 522 usec per loop [me@home]$ python --version Python 2.7.3 

Nota del autor : Esto es ineficiente. Pero divertido, porque los monoides son impresionantes. No es apropiado para el código de producción de Python.

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

Esto simplemente sum los elementos de iterable pasados ​​en el primer argumento, considerando el segundo argumento como el valor inicial de la sum (si no se proporciona, se usa 0 su lugar y este caso le dará un error).

Dado que está sumndo listas anidadas, en realidad obtiene [1,3]+[2,4] como resultado de la 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.

Probé la mayoría de las soluciones sugeridas con perfplot (un proyecto mío, esencialmente una envoltura alrededor de timeit ), y encontré

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

Ser la solución más rápida. ( operator.iadd es igualmente rápido.)

introduzca la descripción de la imagen aquí


Código para reproducir la ttwig:

 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)], logx=True, logy=True, xlabel='num lists' ) 
 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 lugar de devolver un valor útil (que reduce() espera).

Una forma más rápida de hacer la versión reducida 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] 

Este es un enfoque general que se aplica a números , cadenas , listas anidadas y contenedores mixtos .

Código

 from collections import Iterable 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 

Nota: en Python 3, el yield from flatten(x) puede reemplazar for sub_x in flatten(x): yield sub_x

Manifestación

 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'] 

Referencia

  • Esta solución se modifica a partir de una receta en Beazley, D. y B. Jones. Receta 4.14, Python Cookbook 3rd Ed., O’Reilly Media Inc. Sebastopol, CA: 2013.
  • Encontré una publicación anterior de SO , posiblemente la demostración original.

Retiro mi statement. La sum no es la ganadora. 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 sum todavía se está ejecutando por más de un minuto y todavía 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: número = 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 

Si desea aplanar una estructura de datos en la que no sabe a qué profundidad está anidada, 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 una list o iterar explícitamente sobre él.


Para aplanar solo un nivel y si cada uno de los elementos es iterable en sí mismo, también puede usar iteration_utilities.flatten que es solo 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 (según la respuesta de Nico Schlömer que no incluyó la función presentada en esta respuesta):

introduzca la descripción de la imagen aquí

Es un gráfico log-log para adaptarse a la amplia gama de valores abarcados. Para el razonamiento cualitativo: bajar es mejor.

Los resultados muestran que si el iterable contiene solo unos pocos iterables internos, la sum será más rápida, sin embargo, para iterables largos solo los itertools.chain.from_iterable , itertools.chain.from_iterable o la comprensión anidada tienen un rendimiento razonable con itertools.chain.from_iterable siendo el más rápido (como ya lo 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 el autor de esa biblioteca

¿Por qué usas extender?

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

Esto debería funcionar bien.

Parece que hay una confusión con operator.add ! Cuando agrega dos listas juntas, el término correcto para eso es concat , no add. operator.concat es lo que necesitas usar.

Si estás pensando en funcional, es tan fácil como esto:

 >>> 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 el tipo de secuencia, por lo que cuando suministra una tupla, recupera una tupla. vamos a intentar 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á, vuelves una lista.

¿Qué tal el 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 hay comparación para 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 

La razón por la que su función no funcionó: la extensión extiende la matriz en el lugar y no la devuelve. Aún puedes devolver x desde lambda, usando algún truco:

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

Nota: extender es más eficiente que + en las listas.

Considere instalar el paquete more_itertools .

 > pip install more_itertools 

Se envía con una implementación para flatten ( fuente , de las 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 los iterables más complicados y nesteds con more_itertools.collapse ( fuente , contribuido 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] 
 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] 

Una mala característica de la función de Anil anterior es que requiere que el usuario siempre especifique manualmente que el segundo argumento 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] 

Los siguientes me parecen más simples:

 >>> 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] 

matplotlib.cbook.flatten() funcionará para listas anidadas incluso si se 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 ._. Aplanar:

 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 

La respuesta aceptada no me funcionó al tratar 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'] 

También se puede usar el piso 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.

Puedes usar numpy:
flat_list = list(np.concatenate(list_of_list))

Código simple para el fan del paquete underscore.py

 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 anidación compleja)

 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] 

Puedes instalar underscore.py con pip

 pip install underscore.py