Python: forma más rápida de crear una lista de n listas

97

Entonces me preguntaba cómo crear mejor una lista de listas en blanco:

[[],[],[]...]

Debido a cómo Python trabaja con listas en la memoria, esto no funciona:

[[]]*n

Esto crea [[],[],...]pero cada elemento es la misma lista:

d = [[]]*n
d[0].append(1)
#[[1],[1],...]

Algo así como la comprensión de una lista funciona:

d = [[] for x in xrange(0,n)]

Pero esto usa la máquina virtual Python para hacer bucles. ¿Hay alguna forma de usar un bucle implícito (aprovechando que está escrito en C)?

d = []
map(lambda n: d.append([]),xrange(0,10))

En realidad, esto es más lento. :(

munchybunch
fuente
1
Me sorprendería que hubiera algo sustancialmente más rápido que d = [[] for x in xrange(0,n)]. Debe realizar un bucle explícito en Python o llamar a una función / lambda de Python repetidamente (lo que debería ser más lento). Pero aún espero que alguien publique algo que demuestre que estoy equivocado :).
MAK
1
Cuando los midió con timeit, ¿qué aprendió?
S.Lott
Acabo de confirmar que map(lambda x: [], xrange(n))es más lento que la comprensión de una lista.
Andrew Clark

Respuestas:

105

Probablemente la única forma que es marginalmente más rápida que

d = [[] for x in xrange(n)]

es

from itertools import repeat
d = [[] for i in repeat(None, n)]

No tiene que crear un nuevo intobjeto en cada iteración y es aproximadamente un 15% más rápido en mi máquina.

Editar : usando NumPy, puede evitar el bucle de Python usando

d = numpy.empty((n, 0)).tolist()

pero esto es en realidad 2,5 veces más lento que la comprensión de la lista.

Sven Marnach
fuente
¿Qué tal map(lambda x:[], repeat(None,n))?
PaulMcG
3
@Paul: Eso sería mucho más lento nuevamente debido a la sobrecarga de llamada de función de la expresión lambda.
Sven Marnach
¿No debería actualizarse ahora que el rango es diferente en Python 3?
beruic
@beruic Solo estoy citando el código de la pregunta en el primer bloque de código, por lo que realmente no tiene sentido cambiar eso.
Sven Marnach
12

Las comprensiones de la lista en realidad se implementan de manera más eficiente que el bucle explícito (vea la dissalida para funciones de ejemplo ) y la mapforma tiene que invocar un objeto invocable ophaque en cada iteración, lo que incurre en una sobrecarga considerable.

Independientemente, [[] for _dummy in xrange(n)]es la forma correcta de hacerlo y ninguna de las pequeñas diferencias de velocidad (si es que existen) entre varias otras formas debería importar. A menos que, por supuesto, pases la mayor parte del tiempo haciendo esto, pero en ese caso, deberías trabajar en tus algoritmos. ¿Con qué frecuencia crea estas listas?


fuente
5
¡Por favor, no _como nombre de variable! De lo contrario, buena respuesta :)
Sven Marnach
11
@Sven: ¿Por qué no? Se usa comúnmente para variables no utilizadas (si se llamara i, yo estaría buscando dónde se usa). El único inconveniente sería que oculta la _celebración del último resultado en el REPL ... y ese es solo el caso en 2.x donde se filtran las listas por comprensión.
No muy a menudo, por eso seguí adelante y usé una lista de comprensión. Sin embargo, pensé que sería interesante ver lo que la gente tenía que decir. Vi a Dropbox hablar en PyCon con el uso de itertools.imap en lugar de un bucle for para actualizar un hash md5 una cantidad absurdamente grande de veces, y he estado un poco obsesionado con los bucles C desde entonces.
munchybunch
12
La razón más importante para no usarlo es que tiende a confundir a la gente, haciéndoles pensar que es algún tipo de sintaxis especial. Y además del conflicto con _el intérprete interactivo, también entra en conflicto con el alias común de gettext. Si quiere dejar en claro que la variable es una variable ficticia, llámela dummy, no _.
Sven Marnach
12

Aquí hay dos métodos, uno dulce y simple (y conceptual), el otro más formal y puede extenderse en una variedad de situaciones, después de haber leído un conjunto de datos.

Método 1: Conceptual

X2=[]
X1=[1,2,3]
X2.append(X1)
X3=[4,5,6]
X2.append(X3)
X2 thus has [[1,2,3],[4,5,6]] ie a list of lists. 

Método 2: Formal y extensible

Otra forma elegante de almacenar una lista como una lista de listas de diferentes números, que lee de un archivo. (El archivo aquí tiene el tren del conjunto de datos) El tren es un conjunto de datos con, por ejemplo, 50 filas y 20 columnas. es decir. Train [0] me da la primera fila de un archivo csv, train [1] me da la segunda fila y así sucesivamente. Estoy interesado en separar el conjunto de datos con 50 filas como una lista, excepto la columna 0, que es mi variable explicada aquí, por lo que debe eliminarse del conjunto de datos del tren original y luego escalar lista tras lista, es decir, una lista de una lista . Aquí está el código que hace eso.

Tenga en cuenta que estoy leyendo desde "1" en el bucle interno, ya que solo estoy interesado en variables explicativas. Y reinicializo X1 = [] en el otro bucle, de lo contrario, X2.append ([0: (len (train [0]) - 1)]) reescribirá X1 una y otra vez, además de que es más eficiente en memoria.

X2=[]
for j in range(0,len(train)):
    X1=[]
    for k in range(1,len(train[0])):
        txt2=train[j][k]
        X1.append(txt2)
    X2.append(X1[0:(len(train[0])-1)])
ekta
fuente
4

Para crear una lista y una lista de listas, utilice la siguiente sintaxis

     x = [[] for i in range(10)]

esto creará una lista 1-d y para inicializarla, coloque el número en [[número] y establezca la longitud de la lista coloque la longitud en el rango (longitud)

  • Para crear una lista de listas, utilice la siguiente sintaxis.
    x = [[[0] for i in range(3)] for i in range(10)]

esto inicializará la lista de listas con dimensión 10 * 3 y con valor 0

  • Para acceder / manipular el elemento
    x[1][5]=value
waqas ahmed
fuente
2

Así que hice algunas comparaciones de velocidad para conseguir la forma más rápida. Las comprensiones de listas son realmente muy rápidas. La única forma de acercarse es evitar que el código de bytes se ejecute durante la construcción de la lista. Mi primer intento fue el siguiente método, que parecería ser más rápido en principio:

l = [[]]
for _ in range(n): l.extend(map(list,l))

(produce una lista de longitud 2 ** n, por supuesto) Esta construcción es dos veces más lenta que la comprensión de la lista, según timeit, tanto para listas cortas como largas (un millón).

Mi segundo intento fue usar el mapa de estrellas para llamar al constructor de listas por mí.Hay una construcción, que parece ejecutar el constructor de listas a máxima velocidad, pero aún es más lento, pero solo en una pequeña cantidad:

from itertools import starmap
l = list(starmap(list,[()]*(1<<n)))

Es bastante interesante que el tiempo de ejecución sugiere que es la última llamada a la lista la que hace que la solución de mapa estelar sea lenta, ya que su tiempo de ejecución es casi exactamente igual a la velocidad de:

l = list([] for _ in range(1<<n))

Mi tercer intento llegó cuando me di cuenta de que list (()) también produce una lista, así que probé lo aparentemente simple:

l = list(map(list, [()]*(1<<n)))

pero esto fue más lento que la llamada del mapa estelar.

Conclusión: para los maníacos de la velocidad: use la lista de comprensión. Solo llame a funciones, si es necesario. Utilice incorporados.

Jurjen Bos
fuente