simples - ordenar lista de listas python
Hacer una lista plana de la lista de listas en Python (30)
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''
¿Por qué usas extender?
reduce(lambda x, y: x+y, l)
Esto debería funcionar bien.
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
Considere instalar el paquete more_itertools
.
> pip install more_itertools
Se envía con una implementación para flatten
( source , 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 iterables más complicados con more_itertools.collapse
( source , 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]
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 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'']
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]
Otro enfoque inusual que funciona para listas de enteros heterogéneas y homogéneas:
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('','')]
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:
[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
Puedes usar numpy:
flat_list = list(np.concatenate(list_of_list))
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
También se puede usar el flat 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.
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''
)
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
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
. six
Tambié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,
6
es solo un escalar; No es iterable, por lo que las rutas anteriores fallarán aquí. - Uno de los elementos,
''abc''
, es técnicamente iterable (todosstr
s 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 ychain.from_iterable
solo 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".
Retiro mi declaración. La suma 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 suma todavía se está ejecutando 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 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
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)))
Puede que esta no sea la forma más eficiente, pero pensé en colocar una línea (en realidad, una línea doble). Ambas versiones funcionarán en listas anidadas de jerarquías arbitrarias y explotarán las características del lenguaje (Python3.5) y la recursión.
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 de una primera manera profunda. La recursión disminuye hasta que encuentra un elemento que no está en la lista, luego extiende la variable local flist
y luego la devuelve al elemento primario. Cada vez que flist
se devuelve, se extiende a los padres flist
en la lista de comprensión. 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 padres. Creo que el camino para esto puede estar creando un gloabl flist
, como abajo.
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.
Un método recursivo simple que usa reduce
desde functools
y el add
operador 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 flatten
toma lst
como parámetro. Se bucles de todos los elementos de lst
hasta números enteros alcance (también pueden cambiar int
a 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 for
bucles 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:])
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]
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.
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]