Qual è la sintassi idiomatica per anteporre a un breve elenco Python?


543

list.append()è la scelta ovvia da aggiungere alla fine di un elenco. Ecco una spiegazione ragionevole per i dispersi list.prepend(). Supponendo che il mio elenco sia breve e che le preoccupazioni relative alle prestazioni siano trascurabili, lo è

list.insert(0, x)

o

list[0:0] = [x]

idiomatico?

Risposte:


784

Il s.insert(0, x)modulo è il più comune.

Ogni volta che lo vedi, potrebbe essere il momento di considerare l'utilizzo di un collections.deque invece di un elenco.


9
"Ogni volta che lo vedi, potrebbe essere il momento di prendere in considerazione l'utilizzo di un Collections.deque invece di un elenco." Perchè è questo?
Matt M.

6
@MattM. Se si inserisce all'inizio di un elenco, Python deve spostare tutti gli altri elementi di uno spazio in avanti, gli elenchi non possono "fare spazio in primo piano". collections.deque (coda a doppia estremità) ha il supporto per "fare spazio nella parte anteriore" ed è molto più veloce in questo caso.
fejfo

266

Se puoi seguire la strada funzionale, è abbastanza chiaro quanto segue

new_list = [x] + your_list

Naturalmente non avete inserito xin your_list, piuttosto è stato creato un nuovo elenco con xpreprended ad esso.


45
Come vedi, ciò non sta anteponendo a un elenco. Sta creando un nuovo elenco. Quindi non soddisfa affatto la domanda.
Chris Morgan,

113
Anche se non soddisfa la domanda, la completa e questo è lo scopo di questo sito Web. Apprezzo il commento e hai ragione, ma quando le persone lo cercano, è utile vederlo.
dave4jr,

2
Inoltre, se si desidera anteporre un elenco a un elenco, l'utilizzo di insert non funzionerà come previsto. ma questo metodo lo fa!
gota,

90

Qual è la sintassi idiomatica per anteporre a un breve elenco Python?

Di solito non vuoi anteporre ripetutamente a un elenco in Python.

Se è corto e non lo fai molto ... allora ok.

list.insert

Il list.insertpuò essere usato in questo modo.

list.insert(0, x)

Ma questo è inefficiente, perché in Python, a listè un array di puntatori e Python ora deve prendere ogni puntatore nell'elenco e spostarlo di uno in basso per inserire il puntatore al tuo oggetto nel primo slot, quindi è davvero solo efficiente per elenchi piuttosto brevi, come chiedete.

Ecco uno snippet dal sorgente CPython in cui questo è implementato - e come puoi vedere, iniziamo alla fine dell'array e spostiamo tutto in basso di uno per ogni inserimento:

for (i = n; --i >= where; )
    items[i+1] = items[i];

Se si desidera un contenitore / elenco efficiente per anteporre elementi, si desidera un elenco collegato. Python ha una lista doppiamente collegata, che può inserire all'inizio e alla fine in fretta - si chiama deque.

deque.appendleft

A collections.dequeha molti dei metodi di un elenco. list.sortè un'eccezione, rendendo dequedefinitivamente non completamente sostituibile Liskov list.

>>> set(dir(list)) - set(dir(deque))
{'sort'}

Il dequeha anche un appendleftmetodo (nonché popleft). Il dequeè un Deque e una lista doppiamente legata - non importa la lunghezza, ci vuole sempre la stessa quantità di tempo per preprend qualcosa. Nella notazione O grande, O (1) rispetto al tempo O (n) per gli elenchi. Ecco l'uso:

>>> import collections
>>> d = collections.deque('1234')
>>> d
deque(['1', '2', '3', '4'])
>>> d.appendleft('0')
>>> d
deque(['0', '1', '2', '3', '4'])

deque.extendleft

Altrettanto rilevante è il extendleftmetodo del deque , che antepone in modo iterativo:

>>> from collections import deque
>>> d2 = deque('def')
>>> d2.extendleft('cba')
>>> d2
deque(['a', 'b', 'c', 'd', 'e', 'f'])

Si noti che ogni elemento verrà anteposto uno alla volta, in modo da invertire effettivamente il loro ordine.

Performance di listcontrodeque

Per prima cosa installiamo con alcune anteprime iterative:

import timeit
from collections import deque

def list_insert_0():
    l = []
    for i in range(20):
        l.insert(0, i)

def list_slice_insert():
    l = []
    for i in range(20):
        l[:0] = [i]      # semantically same as list.insert(0, i)

def list_add():
    l = []
    for i in range(20):
        l = [i] + l      # caveat: new list each time

def deque_appendleft():
    d = deque()
    for i in range(20):
        d.appendleft(i)  # semantically same as list.insert(0, i)

def deque_extendleft():
    d = deque()
    d.extendleft(range(20)) # semantically same as deque_appendleft above

e prestazioni:

>>> min(timeit.repeat(list_insert_0))
2.8267281929729506
>>> min(timeit.repeat(list_slice_insert))
2.5210217320127413
>>> min(timeit.repeat(list_add))
2.0641671380144544
>>> min(timeit.repeat(deque_appendleft))
1.5863927800091915
>>> min(timeit.repeat(deque_extendleft))
0.5352169770048931

Il deque è molto più veloce. Man mano che le liste si allungano, mi aspetto che un deque si esibisca ancora meglio. Se puoi usare i deque extendleftprobabilmente otterrai le migliori prestazioni in quel modo.


57

Se qualcuno trova questa domanda come me, ecco i miei test delle prestazioni dei metodi proposti:

Python 2.7.8

In [1]: %timeit ([1]*1000000).insert(0, 0)
100 loops, best of 3: 4.62 ms per loop

In [2]: %timeit ([1]*1000000)[0:0] = [0]
100 loops, best of 3: 4.55 ms per loop

In [3]: %timeit [0] + [1]*1000000
100 loops, best of 3: 8.04 ms per loop

Come puoi vedere, l' insertassegnazione delle sezioni è quasi due volte più veloce dell'aggiunta esplicita e ha risultati molto vicini. Come notato da Raymond Hettingerinsert è l'opzione più comune e io, personalmente preferisco questo modo di anteporre alla lista.


11
Una cosa che manca a quel test è la complessità. Mentre le prime due opzioni hanno una complessità costante (non diventa più lenta quando ci sono più elementi nell'elenco), la terza ha una complessità lineare (diventa più lenta, a seconda della quantità di elementi nell'elenco), perché sempre deve copiare l'intero elenco. Con più elementi nell'elenco, il risultato può peggiorare molto.
Dakkaron,

6
@Dakkaron Penso che ti sbagli. Alcune fonti citano una complessità lineare per list.insert, ad esempio questa bella tabella , e implicita dalla ragionevole spiegazione a cui l'interrogatore si collegava. Sospetto che CPython stia riassegnando ogni elemento in memoria nell'elenco nei primi due casi, quindi probabilmente tutti e tre hanno una complessità lineare. In realtà non ho esaminato il codice o testato me stesso, quindi mi dispiace se quelle fonti sono sbagliate. Collections.deque.appendleft ha la complessità lineare di cui stai parlando.
TC Proctor,

@Dakkaron non è vero, tutti questi hanno una complessità equivalente. Sebbene .inserte [0:0] = [0]opera sul posto , hanno ancora a ri-allocare l'intero buffer.
juanpa.arrivillaga,

Questi benchmark sono sbagliati. L'elenco iniziale deve essere creato in una fase di installazione separata, non parte del tempo stesso. E l'ultimo crea un nuovo elenco lungo 1000001, quindi il confronto con le altre due versioni mutanti sul posto è mele e arance.
mercoledì
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.