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

3379

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

Puedo hacer eso en un forbucle, pero ¿tal vez hay algo genial "one-liner"? Lo intenté con reduce(), 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 "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <lambda>
AttributeError: 'NoneType' object has no attribute 'extend'
Emma
fuente
20
Aquí hay una discusión en profundidad de esto: rightfootin.blogspot.com/2006/09/more-on-python-flatten.html , que discute varios métodos para aplanar listas de listas arbitrariamente anidadas. Una lectura interesante!
RichieHindle
66
Algunas otras respuestas son mejores, pero la razón por la cual la suya falla es que el método 'extender' siempre devuelve Ninguno. Para una lista con longitud 2, funcionará pero no devolverá ninguno. Para una lista más larga, consumirá los primeros 2 args, que devuelve Ninguno. Luego continúa con None.extend (<tercer argumento>), lo que causa este error
mehtunguh
La solución @ shawn-chin es la más pitónica aquí, pero si necesita preservar el tipo de secuencia, digamos que tiene una tupla de tuplas en lugar de una lista de listas, entonces debe usar reduce (operator.concat, tuple_of_tuples). El uso de operator.concat con tuplas parece funcionar más rápido que chain.from_iterables con list.
Meitham

Respuestas:

4799

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. ( les 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 timeit módulo 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 ensum ) son, necesariamente,O(L**2) cuando hay sublistas L, ya que la lista de resultados intermedios se alarga, en cada paso se asigna un nuevo objeto de lista de resultados intermedios y todos los elementos en el resultado intermedio anterior debe copiarse (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, el segundo I ítems L-2 veces, y así sucesivamente; número total de copias es I veces la suma de x para x desde 1 a L excluidos, 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.

Alex Martelli
fuente
486
Probé una prueba con los mismos datos, utilizando 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))'. Funciona un poco más del doble de rápido que la comprensión de la lista anidada que es la más rápida de las alternativas que se muestran aquí.
intuido
274
Encontré la sintaxis difícil de entender hasta que me di cuenta de que puedes pensar en ella exactamente como anidada para bucles. para sublista en l: para ítem en sublista: ítem de rendimiento
Rob Crowell
23
@BorisChervenkov: Observe que envié la llamada list()para realizar el iterador en una lista.
intuido
163
[hoja por árbol en bosque por hoja en árbol] podría ser más fácil de comprender y aplicar.
John Mee el
80
@Joel, en realidad hoy en día list(itertools.chain.from_iterable(l))es mejor, como se observó en otros comentarios y la respuesta de Shawn.
Alex Martelli
1569

Puedes 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()lo 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))
Shawn Chin
fuente
13
Esto *es lo complicado que hace que sea chainmenos sencillo que la comprensión de la lista. Debe saber que la cadena solo une los iterables pasados ​​como parámetros, y el * hace que la lista de nivel superior se expanda en parámetros, por lo que chainune todos esos iterables, pero no desciende más. Creo que esto hace que la comprensión sea más legible que el uso de la cadena en este caso.
Tim Dierks
52
@TimDierks: No estoy seguro de que "esto requiere que comprenda la sintaxis de Python" es un argumento en contra del uso de una técnica dada en Python. Claro, el uso complejo podría confundir, pero el operador "splat" generalmente es útil en muchas circunstancias, y esto no lo está usando de una manera particularmente oscura; Rechazar todas las características del lenguaje que no son necesariamente obvias para los usuarios principiantes significa que estás atando una mano a la espalda. También podría tirar las comprensiones de la lista mientras estás en ello; Los usuarios de otros entornos encontrarían un forbucle que appendes más obvio en repetidas ocasiones .
ShadowRanger
Esta respuesta, y otras respuestas aquí, dan un resultado incorrecto si el nivel superior también contiene un valor. por ejemplo, list = [["abc","bcd"],["cde","def"],"efg"]dará como resultado una salida de["abc", "bcd", "cde", "def", "e", "f", "g"].
gouravkr
Parece que el *operador no se puede usar en python2
wkm hace
908

Nota del autor : Esto es ineficiente. Pero divertido, porque los 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 da, 0se usa en su lugar y este caso le dará un error).

Debido a que estás sumando listas anidadas, en realidad obtienes [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.

Tríptico
fuente
100
eso es bastante ordenado e inteligente, pero no lo usaría porque es confuso de leer.
andrewrk
87
Este es un algoritmo de Shlemiel el pintor joelonsoftware.com/articles/fog0000000319.html , innecesariamente ineficiente y innecesariamente feo.
Mike Graham el
44
La operación de agregar en las listas forma a Monoid, que es una de las abstracciones más convenientes para pensar en una +operación en un sentido general (no se limita solo a números). Así que esta respuesta merece un +1 de mi parte para el tratamiento (correcto) de las listas como un monoide. Sin embargo, el rendimiento es preocupante ...
ulidtko
77
@andrewrk Bueno, algunas personas piensan que esta es la forma más limpia de hacerlo: youtube.com/watch?v=IOiZatlZtGU los que no entienden por qué esto es genial, solo tienen que esperar unas décadas hasta que todos lo hagan de esta manera: ) usemos lenguajes de programación (y abstracciones) que se descubren y no se inventan, se descubre Monoid.
jhegedus
11
Esta es una forma muy ineficiente debido al aspecto cuadrático de la suma.
Jean-François Fabre
461

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

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

para ser la solución más rápida, tanto cuando se concatenan muchas listas pequeñas y pocas listas largas. ( operator.iaddes igualmente rápido)

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí


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,
    # 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)"
)
Nico Schlömer
fuente
25
Para grandes listas anidadas, 'list (numpy.array (a) .flat)' es la más rápida entre todas las funciones anteriores.
Sara
Intenté utilizar regex: 'list (map (int, re.findall (r "[\ w] +", str (a))))'. La velocidad es un poco más lenta que numpy_concatenate
Justas
¿Hay alguna manera de hacer una trama en 3-d? número de matrices por tamaño promedio de matriz?
Leo
Amo tu solución. Corto, simple y eficiente :-)
ShadyMBA
182
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 extend()método en su ejemplo modifica en xlugar de devolver un valor útil (que reduce()espera).

Una forma más rápida de hacer la reduceversión 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]
Greg Hewgill
fuente
20
reduce(operator.add, l)sería la forma correcta de hacer la reduceversión. Los empotrados son más rápidos que las lambdas.
agf
3
@agf aquí es cómo: * 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
Este es un algoritmo de Shlemiel el pintor joelonsoftware.com/articles/fog0000000319.html
Mike Graham
2
esto solo se puede usar para integers. Pero, ¿y si la lista contiene string?
Freddy
3
@Freddy: la operator.addfunción funciona igualmente bien tanto para listas de enteros como para listas de cadenas.
Greg Hewgill
121

No reinvente la rueda si usa 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))
Max Malysh
fuente
44
flatten = itertools.chain.from_iterabledebería ser la respuesta correcta
geckos
3
¡gran respuesta! funciona también para l = [[[1, 2, 3], [4, 5]], 5] en el caso de los pandas
Markus Dutschke
1
Me gusta la solución Pandas. Si usted tiene algo así como: list_of_menuitems = [1, 2, [3, [4, 5, [6]]]], que se traducirá en: [1, 2, 3, 4, 5, 6]. Lo que extraño es el nivel de aplanamiento.
imjoseangel
115

Aquí hay un enfoque general que se aplica a números , cadenas , listas anidadas y contenedores mixtos .

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 desde collection.abcel typingmódulo.

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 SO anterior , posiblemente la demostración original.
pylang
fuente
55
Acabo de escribir más o menos lo mismo, porque no vi su solución ... esto es lo que busqué "aplanar recursivamente listas múltiples completas" ... (+1)
Martin Thoma
3
@MartinThoma Muy apreciado. Para su información, si aplanar iterables anidados es una práctica común para usted, hay algunos paquetes de terceros que manejan esto bien. Esto puede evitar reinventar la rueda. He mencionado more_itertoolsentre otros discutidos en esta publicación. Salud.
pylang
Quizás traversetambién podría ser un buen nombre para esta forma de árbol, mientras que lo mantendría menos universal para esta respuesta al apegarme a las listas anidadas.
Wolf
Puede verificar en if hasattr(x, '__iter__')lugar de importar / verificar Iterabley eso excluirá las cadenas también.
Ryan Allen
el código anterior no parece funcionar si una de las listas anidadas tiene una lista de cadenas. [1, 2, [3, 4], [4], [], 9, 9.5, 'ssssss', ['str', 'sss', 'ss'], [3, 4, 5]] salida: - [1, 2, 3, 4, 4, 9, 9.5, 'ssssss', 3, 4, 5]
soleadoX
51

Si desea aplanar una estructura de datos donde no sabe qué tan profundo está anidado, puede usar 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]

Es un generador, por lo que debe emitir el resultado listo iterarlo explícitamente.


Para aplanar solo un nivel y si cada uno de los elementos es iterable en sí mismo, también puede usar el iteration_utilities.flattencual es solo un envoltorio delgado 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):

ingrese la descripción de la imagen aquí

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 sumserá más rápido, sin embargo, para iterables largos solo el itertools.chain.from_iterable, iteration_utilities.deepflatteno la comprensión anidada tienen un rendimiento razonable al itertools.chain.from_iterableser 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

MSeifert
fuente
sumya no funciona en secuencias arbitrarias, ya que comienza con 0, por lo que functools.reduce(operator.add, sequences)la sustitución (no estamos contentos de que se retiraran reducede órdenes internas?). Cuando se conocen los tipos, puede ser más rápido de usar type.__add__.
Yann Vernier
@YannVernier Gracias por la información. Pensé que ejecuté estos puntos de referencia en Python 3.6 y funcionó sum. ¿Sabes en qué versiones de Python dejó de funcionar?
MSeifert
Estaba un poco equivocado. 0es solo el valor inicial predeterminado, por lo que funciona si uno usa el argumento de inicio para comenzar con una lista vacía ... pero sigue siendo una cadena de casos especiales y me dice que use join. Se está implementando en foldllugar de foldl1. El mismo problema aparece en 2.7.
Yann Vernier
39

Retiro mi declaración. La suma 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
Nadia Alramli
fuente
23
para una lista verdaderamente minúscula, por ejemplo, una con 3 sublistas, tal vez, pero dado que el rendimiento de la suma va con O (N ** 2) mientras que la comprensión de la lista va con O (N), solo aumentar un poco la lista de entrada revertirá las cosas: - de hecho, el LC será "infinitamente más rápido" que la suma en el límite a medida que N crece. Fui responsable de diseñar la suma y hacer su primera implementación en el tiempo de ejecución de Python, y todavía deseo haber encontrado una manera de restringirla efectivamente a la suma de números (lo que es realmente bueno) y bloquear la "molestia atractiva" que ofrece a las personas que quieren "sumar" listas ;-).
Alex Martelli
38

Parece que hay una confusión con operator.add! Cuando agrega dos listas juntas, el término correcto para eso es concatno agregar. operator.concates 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_iterablees bastante rapido! Pero no es una 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
Meitham
fuente
1
Hmm ser segundo ejemplo justo debe ser lista también (o el primer par de valores?)
Mr_and_Mrs_D
2
El uso de entradas tan pequeñas no es una comparación justa. Para 1000 secuencias de longitud 1000, obtengo 0.037 segundos para list(chain.from_iterable(...))y 2.5 segundos para reduce(concat, ...). El problema es que reduce(concat, ...)tiene tiempo de ejecución cuadrático, mientras que chaines lineal.
kaya3
33

¿Por qué usas extender?

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

Esto debería funcionar bien.

Andrea Ambu
fuente
77
para python3from functools import reduce
andorov
Lo sentimos eso es muy lento Ver resto de respuestas
Mr_and_Mrs_D
Esta es, con mucho, la solución más fácil de entender pero corta que funciona en Python 2 y 3. Me doy cuenta de que mucha gente de Python está en el procesamiento de datos donde hay grandes cantidades de datos para procesar y, por lo tanto, se preocupa mucho por la velocidad, pero cuando están escribiendo un script de shell y solo tienen unas pocas docenas de elementos en algunas sublistas, entonces esto es perfecto.
Asfand Qazi
27

Considere instalar el more_itertoolspaquete.

> 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 iterables anidados más complicados 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]
pylang
fuente
En efecto. Esta debería ser la respuesta aceptada
brunetton
Si puede permitirse agregar un paquete a su proyecto, esta respuesta es la mejor
viddik13
22

La razón por la que su función no funcionó es porque la extensión extiende una matriz en el lugar 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.

Igor Krivokon
fuente
77
extendse utiliza mejor como newlist = [], extend = newlist.extend, for sublist in l: extend(l)ya que evita la (bastante grande) por encima de la lambda, la búsqueda atributo en x, y la or.
agf
para python 3 addfrom functools import reduce
Markus Dutschke
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]
Añil
fuente
def flatten(l, a=None): if a is None: a = [][...]
Poik
16

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]
Saurabh Singh
fuente
1
bueno, no se necesitan importaciones y está claro lo que está haciendo ... aplanar una lista, punto :)
Goran B.
1
simplemente brillante!
Sachin Sharma
15

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
EL_DON
fuente
14

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 que propone hizo el trabajo 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']
usuario9074332
fuente
13

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

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]
diablo en los detalles
fuente
No funciona para listas con diferentes dimensiones. -1
nurub
10

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.

mdh
fuente
¿Sería esa la solución óptima?
RetroCode
6

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

A. Attia
fuente
Esto funciona también para listas numéricas, de cadenas y mixtas
Nitin
2
Falla para datos anidados de manera desigual, como[1, 2, [3], [[4]], [5, [6]]]
EL_DON
5

Si está dispuesto a renunciar a una pequeña cantidad de velocidad para una apariencia más limpia, entonces podría 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

mkultra
fuente
1
No funciona para listas anidadas desigualmente como[1, 2, [3], [[4]], [5, [6]]]
EL_DON
5

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)

canuck
fuente
1
Esto está mal, aplanar reducirá las dimensiones de la matriz nd a una, pero no concatenará las listas dentro como una sola.
Ando Jurai
5

Código simple para underscore.pyventilador de paquete

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]

Se puede instalar underscore.pycon pip

pip install underscore.py
Vu Anh
fuente
Del mismo modo, puedes usar pydash . Creo que esta versión es mucho más legible que la comprensión de la lista o cualquier otra respuesta.
gliemezis
2
Esto es super lento.
Nico Schlömer
2
¿Por qué tiene un módulo llamado _? Eso parece un mal nombre. Ver stackoverflow.com/a/5893946/6605826
EL_DON
2
@EL_DON: De la página readme de underscore.py "Underscore.py es un puerto de Python de una excelente biblioteca de JavaScript underscore.js". Creo que es la razón de este nombre. Y sí, no es un buen nombre para 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:])
englealuze
fuente
Falla para python2.7 para la lista anidada de ejemplo en la pregunta:[[1, 2, 3], [4, 5, 6], [7], [8, 9]]
EL_DON
@EL_DON probado en python 2.7.5. funciona bien
englealuze
5

Nota : A continuación se aplica a Python 3.3+ porque lo usa yield_from. sixTambién es un paquete de terceros, aunque es estable. Alternativamente, podrías usar sys.version.


En el caso de obj = [[1, 2,], [3, 4], [5, 6]], todas las soluciones aquí son buenas, incluida la comprensión de listas y itertools.chain.from_iterable.

Sin embargo, considere este caso un poco más complejo:

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

Hay varios problemas aquí:

  • Un elemento, 6es solo un escalar; no es iterable, por lo que las rutas anteriores fallarán aquí.
  • Uno de los elementos, 'abc', es técnicamente iterable (todos strs son). Sin embargo, al leer un poco entre líneas, no desea tratarlo como tal, desea tratarlo como un solo elemento.
  • El elemento final, [8, [9, 10]]es en sí mismo un iterable anidado. Lista de comprensión básica y chain.from_iterablesolo extrae "1 nivel abajo".

Puede remediar esto de la siguiente manera:

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

Aquí, verifica que el subelemento (1) sea iterable con Iterableun ABC de itertools, pero también quiere asegurarse de que (2) el elemento no sea "similar a una cadena".

Brad Solomon
fuente
1
Si todavía está interesado en la compatibilidad con Python 2, cambie yield froma un forbucle, por ejemplofor x in flatten(i): yield x
pylang
5
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.

Deepak Yadav
fuente
5
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.

Alijy
fuente
La pregunta establece una "lista de listas", pero su lista de ejemplo incluye un elemento que no es una lista. La mayoría de las otras soluciones se apegan a la pregunta original. Su solución resuelve un problema más amplio, pero también requiere un paquete de Python no base (nltk) que debe instalarse primero.
simonobo
4

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 explotarán 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 flisty luego la revierte al padre. Cada vez que flistse devuelve, se extiende a los padres flisten 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 forma de evitar 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.

foxis
fuente
¿Por qué extender ([l]) en lugar de agregar (l)?
Maciek
3

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(',')]
tharndt
fuente
Esa es solo una forma más complicada y un poco más lenta de lo que ᴡʜᴀᴄᴋᴀᴍᴀᴅᴏᴏᴅʟᴇ3000 ya publicó antes. Reinventé su propuesta ayer, así que este enfoque parece bastante popular en estos días;)
Darkonaut
No del todo: 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
mi código como una línea sería: flat_list = [int(e.replace('[','').replace(']','')) for e in str(deep_list).split(',')]
tharndt
1
De hecho, tienes razón +1, la propuesta de ᴡʜᴀᴄᴋᴀᴍᴀᴅᴏᴏᴅʟᴇ3000 no funcionará con números de varios dígitos, tampoco probé esto antes aunque debería ser obvio. Podrías simplificar tu código y escribir [int(e.strip('[ ]')) for e in str(deep_list).split(',')]. Pero sugeriría seguir con la propuesta de Deleet para casos de uso real. No contiene transformaciones de tipo hacky, es más rápido y más versátil porque, naturalmente, también maneja listas con tipos mixtos.
Darkonaut
2
Lamentablemente no. Pero vi este código recientemente aquí: Python Practice Book 6.1.2
tharndt