simples - Hacer una lista plana de la lista de listas en Python




organizar listas python (20)

¿Por qué usas extender?

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

Esto debería funcionar bien.

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 ¿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 "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <lambda>
AttributeError: 'NoneType' object has no attribute 'extend'

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

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.

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.


La solución más rápida que he encontrado (para la 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)


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]

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ás reduce el tipo de secuencia, de modo que cuando suministras una tupla, recuperas 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

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:

[[email protected]]$ 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
[[email protected]]$ 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
[[email protected]]$ 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
[[email protected]]$ 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
[[email protected]]$ python --version
Python 2.7.3

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 (basado en la respuesta de Nico Schlömer que no incluyó la función presentada en esta respuesta):

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

Los resultados muestran que si el iterable contiene solo unos pocos iterables internos, entonces 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


Si está dispuesto a renunciar a una pequeña cantidad de velocidad para un aspecto más limpio, 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


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]

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

list(itertools.chain.from_iterable(a))

para ser la solución más rápida (si se concatenan más de 10 listas).

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

Nota del autor : Esto es ineficiente. Pero divertido, porque las mónadas 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 solo suma los elementos de iterable pasados ​​en el primer argumento, considerando el segundo argumento como el valor inicial de la suma (si no se proporciona, se usa 0 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 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.


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

En el caso de obj = [[1, 2,], [3, 4], [5, 6]], todas las soluciones aquí son buenas, incluida la comprensión de la lista 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, leyendo un poco entre líneas, no querrás tratarlo como tal, sino tratarlo como un elemento único.
  • El elemento final, [8, [9, 10]]es en sí mismo un iterable anidado. Comprensión de la lista básica y chain.from_iterablesolo extraer "1 nivel hacia abajo".

Puedes 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 Iterable, un ABC de itertools, pero también quiere asegurarse de que (2) el elemento no sea "como una cadena".


Hace poco me encontré con una situación en la que tenía una combinación de cadenas y datos numéricos en listas tales como

test = ['591212948',
['special', 'assoc', 'of', 'Chicago', 'Jon', 'Doe'],
['Jon'],
['Doe'],
['fl'],
92001,
555555555,
'hello',
['hello2', 'a'],
'b',
['hello33', ['z', 'w'], 'b']]

Donde los métodos como flat_list = [item for sublist in test for item in sublist]no han funcionado. Entonces, se me ocurrió la siguiente solución para el nivel 1+ de sublistas

def concatList(data):
    results = []
    for rec in data:
        if type(rec) == list:
            results += rec
            results = concatList(results)
        else:
            results.append(rec)
    return results

Y el resultado

In [38]: concatList(test)
Out[38]:
 Out[60]:
['591212948',
'special',
'assoc',
'of',
'Chicago',
'Jon',
'Doe',
'Jon',
'Doe',
'fl',
92001,
555555555,
'hello',
'hello2',
'a',
'b',
'hello33',
'z',
'w',
'b']

Puede evitar las llamadas recursivas a la pila utilizando una estructura de datos de pila real de manera bastante simple.

alist = [1,[1,2],[1,2,[4,5,6],3, "33"]]
newlist = []

while len(alist) > 0 :
  templist = alist.pop()
  if type(templist) == type(list()) :
    while len(templist) > 0 :
      temp = templist.pop()
      if type(temp) == type(list()) :
        for x in temp :
          templist.append(x)
      else :
        newlist.append(temp)
  else :
    newlist.append(templist)
print(list(reversed(newlist)))

Un método recursivo simple que usa reducedesde functoolsy el addoperador en las listas:

>>> from functools import reduce
>>> from operator import add
>>> flatten = lambda lst: [lst] if type(lst) is int else reduce(add, [flatten(ele) for ele in lst])
>>> flatten(l)
[1, 2, 3, 4, 5, 6, 7, 8, 9]

La función flattentoma lstcomo parámetro. Se bucles de todos los elementos de lsthasta números enteros alcance (también pueden cambiar inta float, str, etc. para otros tipos de datos), que se añaden al valor de retorno de la recursión más externa.

La recursión, a diferencia de los métodos como los forbucles y las mónadas, es que es una solución general no limitada por la profundidad de la lista . Por ejemplo, una lista con una profundidad de 5 se puede aplanar de la misma manera que l:

>>> l2 = [[3, [1, 2], [[[6], 5], 4, 0], 7, [[8]], [9, 10]]]
>>> flatten(l2)
[3, 1, 2, 6, 5, 4, 0, 7, 8, 9, 10]

def flatten(alist):
    if alist == []:
        return []
    elif type(alist) is not list:
        return [alist]
    else:
        return flatten(alist[0]) + flatten(alist[1:])

flat_list = []
for i in list_of_list:
    flat_list+=i

Este Código también funciona bien, ya que solo extiende la lista hasta el final. Aunque es muy similar pero solo tiene uno para bucle. Así que tiene menos complejidad que agregar 2 para bucles.


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]

Como prueba, como siempre, 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 de generalidad real) digamos que tiene L sublistas de I elementos cada uno: los primeros elementos I se copian una y otra vez L-1 veces, el segundo I elementos L-2 veces, y así sucesivamente; el número total de copias es I veces la suma 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) también una sola vez.





flatten