¿Cómo hago una lista plana a partir de una lista de listas?

Resuelto Emma asked hace 15 años • 32 respuestas

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

Emma avatar Jun 05 '09 03:06 Emma
Aceptado

Una lista de listas nombradas xssse 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 timeitmó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.

Alex Martelli avatar Jun 04 '2009 20:06 Alex Martelli

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
Shawn Chin avatar Jun 04 '2009 21:06 Shawn Chin

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]

sumsuma los elementos del iterable xssy 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.

Kenan Banks avatar Jun 04 '2009 20:06 Kenan Banks

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

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 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()
Nico Schlömer avatar Jul 26 '2017 09:07 Nico Schlömer

Usando functools.reduce, que agrega una lista acumulada xsa 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]
Greg Hewgill avatar Jun 04 '2009 20:06 Greg Hewgill

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.abcmódulo typing.

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.
pylang avatar Nov 29 '2016 04:11 pylang