Pregunta Hacer una lista plana de la lista de listas en Python


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

Puedo hacer eso en un bucle for, pero tal vez haya 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 "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <lambda>
AttributeError: 'NoneType' object has no attribute 'extend'

2069
2018-06-04 20:30


origen


Respuestas:


flat_list = [item for sublist in l for item in sublist]

lo que significa:

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]

Para evidencia, como siempre, 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 en sum) son, por necesidad, O(L**2) cuando hay L sublistas: a medida que la lista de resultados intermedia se hace más larga, en cada paso se asigna un nuevo objeto de lista de resultados intermedios y se deben copiar todos los elementos en el resultado intermedio anterior (así como algunos nuevos agregados) al final). Entonces (por simplicidad y sin pérdida de generalidad) diga que tiene L sublistas de I elementos cada una: los primeros I elementos se copian de ida y vuelta L-1 veces, el segundo I artículos 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 lista de comprensión solo 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.


2984
2018-06-04 20:37



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

[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

1079
2018-06-04 21:06



Nota del autor: Esto es ineficiente. Pero divertido, porque las mónadas 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 simplemente resume los elementos de iterable pasado en el primer argumento, tratando el segundo argumento como el valor inicial de la suma (si no se da, 0 se usa en su lugar y este caso le dará un error).

Como 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.


636
2018-06-04 20:35



Probé la mayoría de las soluciones sugeridas con Perfplot (un proyecto mío favorito, esencialmente un envoltorio alrededor timeit), y encontrado

list(itertools.chain.from_iterable(a))

ser la solución más rápida (si hay más de 10 listas concatenadas).

enter image description here


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 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, itertools_chain, numpy_flat,
        numpy_concatenate
        ],
    n_range=[2**k for k in range(16)],
    logx=True,
    logy=True,
    xlabel='num lists'
    )

129
2017-07-26 09:38



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]

los extend() método en su ejemplo modifica x en lugar de devolver un valor útil (que reduce() espera).

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

99
2018-06-04 20:35



Aquí hay un enfoque general que se aplica a números, instrumentos de cuerda, anidado listas y mezclado contenedores.

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, yield from flatten(x) puede sustituir 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.
  • Encontrado un anterior SO publicación, posiblemente la demostración original.

54
2017-11-29 04:14



Tomo mi declaración de vuelta. 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 sigue ejecutándose durante 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 listas pequeñas 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

31
2018-06-04 20:46



¿Por qué usas extender?

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

Esto debería funcionar bien.


25
2018-06-04 20:38



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

Si piensas que es 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 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 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

19
2017-09-14 15:09



Si desea aplanar una estructura de datos en la que no sabe qué tan profundo está anidado, podría usar iteration_utilities.deepflatten1

>>> 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 enviar el resultado a un list o iterar explícitamente sobre él.


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 solo una fina capa alrededor 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):

enter image description here

Es un diagrama de registro y registro para acomodar el amplio rango de valores abarcados. Para el razonamiento cualitativo: más bajo es mejor.

Los resultados muestran que si el iterable contiene solo algunos iterables internos, entonces sum será más rápido, sin embargo, para los iterables largos solo los itertools.chain.from_iterable, iteration_utilities.deepflatten o la comprensión anidada tiene 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


17
2017-11-26 00:20



La razón por la cual su función no funcionó: la extensión extiende la matriz in situ y no la devuelve. Todavía puede devolver x de lambda, usando algún truco:

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

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


13
2018-06-04 20:47