Python: il modo più veloce per creare un elenco di n elenchi


97

Quindi mi chiedevo come creare al meglio un elenco di elenchi vuoti:

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

A causa di come Python funziona con gli elenchi in memoria, questo non funziona:

[[]]*n

Questo crea [[],[],...]ma ogni elemento è lo stesso elenco:

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

Qualcosa di simile alla comprensione di una lista funziona:

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

Ma questo usa la VM Python per il loop. C'è un modo per usare un ciclo implicito (approfittando del fatto che è scritto in C)?

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

Questo è effettivamente più lento. :(


1
Sarei sorpreso se ci fosse qualcosa di sostanzialmente più veloce di d = [[] for x in xrange(0,n)]. Devi eseguire il loop esplicitamente in Python o chiamare ripetutamente una funzione / lambda Python (che dovrebbe essere più lento). Ma spero ancora che qualcuno pubblichi qualcosa che dimostri che ho sbagliato :).
MAK

1
Quando li hai misurati con timeit, cosa hai imparato?
S.Lott

Ho appena confermato che map(lambda x: [], xrange(n))è più lento della comprensione di una lista.
Andrew Clark

Risposte:


105

Probabilmente l'unico modo che è leggermente più veloce di

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

è

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

Non è necessario creare un nuovo intoggetto in ogni iterazione ed è circa il 15% più veloce sulla mia macchina.

Modifica : usando NumPy, puoi evitare il ciclo Python usando

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

ma questo è in realtà 2,5 volte più lento della comprensione dell'elenco.


Che ne dici map(lambda x:[], repeat(None,n))?
PaulMcG

3
@ Paul: Sarebbe di nuovo molto più lento a causa dell'overhead della chiamata di funzione dell'espressione lambda.
Sven Marnach

Non dovrebbe essere aggiornato ora che l'intervallo è diverso in Python 3?
beruic

@beruic Sto solo citando il codice dalla domanda nel primo blocco di codice, quindi non ha davvero senso cambiarlo.
Sven Marnach

12

Le comprensioni della lista in realtà sono implementate in modo più efficiente del ciclo esplicito (vedere l' disoutput per le funzioni di esempio ) e il mapmodo deve invocare un oggetto richiamabile opaco ad ogni iterazione, il che comporta un sovraccarico considerevole.

Indipendentemente da ciò, [[] for _dummy in xrange(n)]è il modo giusto per farlo e nessuna delle minuscole (se presenti) differenze di velocità tra i vari altri modi dovrebbe avere importanza. A meno che, naturalmente, non passi la maggior parte del tuo tempo a farlo, ma in tal caso, dovresti invece lavorare sui tuoi algoritmi. Con che frequenza crei questi elenchi?


5
Per favore, no _come nome di variabile! Altrimenti bella risposta :)
Sven Marnach

11
@Sven: perché no? È comunemente usato per le variabili inutilizzate (se fosse chiamato i, io per primo cercherei dove è usato). L'unico problema sarebbe che oscura il _mantenimento dell'ultimo risultato nella REPL ... e questo è solo il caso in 2.x in cui le liste di comprensione trapelano.

Non molto spesso, motivo per cui sono andato avanti e ho utilizzato una comprensione delle liste. Ho pensato che sarebbe stato interessante vedere cosa avevano da dire le persone. Ho visto il discorso di Dropbox a PyCon con l'uso di itertools.imap invece di un ciclo for per aggiornare un hash md5 una quantità assurdamente grande di volte, e da allora sono stato un po 'ossessionato dai loop C.
munchybunch

12
Il motivo più importante per non usarlo è che tende a confondere le persone, facendole pensare che sia una sorta di sintassi speciale. E oltre al conflitto con _nell'interprete interattivo, è anche in conflitto con l'alias gettext comune. Se vuoi chiarire che la variabile è una variabile fittizia, chiamala dummy, no _.
Sven Marnach

12

Ecco due metodi, uno dolce e semplice (e concettuale), l'altro più formale e può essere esteso in una varietà di situazioni, dopo aver letto un dataset.

Metodo 1: concettuale

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. 

Metodo 2: formale ed estensibile

Un altro modo elegante per memorizzare un elenco come un elenco di elenchi di numeri diversi, che viene letto da un file. (Il file qui ha il set di dati treno) Train è un set di dati con diciamo 50 righe e 20 colonne. cioè. Train [0] mi dà la prima riga di un file CSV, train [1] mi dà la seconda riga e così via. Sono interessato a separare il set di dati con 50 righe come un unico elenco, ad eccezione della colonna 0, che è la mia variabile spiegata qui, quindi deve essere rimosso dal set di dati del treno originale, e quindi ridimensionare l'elenco dopo l'elenco, ovvero un elenco di un elenco . Ecco il codice che lo fa.

Nota che sto leggendo da "1" nel ciclo interno poiché sono interessato solo alle variabili esplicative. E reinizializzo X1 = [] nell'altro ciclo, altrimenti X2.append ([0: (len (train [0]) - 1)]) riscriverà X1 più e più volte - inoltre è più efficiente in termini di 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)])

4

Per creare un elenco e un elenco di elenchi, utilizzare la sintassi seguente

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

questo creerà una lista 1-d e per inizializzarla metti il ​​numero in [[numero] e imposta la lunghezza della lista metti la lunghezza nell'intervallo (lunghezza)

  • Per creare un elenco di elenchi, utilizzare la sintassi seguente.
    x = [[[0] for i in range(3)] for i in range(10)]

questo inizializzerà l'elenco di elenchi con dimensione 10 * 3 e con valore 0

  • Per accedere / manipolare l'elemento
    x[1][5]=value

2

Quindi ho fatto alcuni confronti di velocità per ottenere il modo più veloce. La comprensione delle liste è davvero molto veloce. L'unico modo per avvicinarsi è evitare che il bytecode venga eliminato durante la costruzione della lista. Il mio primo tentativo è stato il seguente metodo, che in linea di principio sembrerebbe più veloce:

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

(produce una lista di lunghezza 2 ** n, ovviamente) Questa costruzione è due volte più lenta della comprensione della lista, secondo timeit, sia per liste brevi che lunghe (un milione).

Il mio secondo tentativo è stato quello di utilizzare la mappa stellare per chiamare il costruttore dell'elenco per me, c'è una costruzione, che sembra eseguire il costruttore dell'elenco alla massima velocità, ma è ancora più lento, ma solo di una piccola quantità:

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

Abbastanza interessante il tempo di esecuzione suggerisce che è la chiamata finale della lista che rende lenta la soluzione starmap, poiché il suo tempo di esecuzione è quasi esattamente uguale alla velocità di:

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

Il mio terzo tentativo è arrivato quando mi sono reso conto che list (()) produce anche un elenco, quindi ho provato l'apperentemente semplice:

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

ma questo era più lento della chiamata starmap.

Conclusione: per i maniaci della velocità: utilizzare la comprensione della lista. Chiama solo funzioni, se necessario. Usa incorporati.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.