Python: List vs Dict per la tabella di ricerca


169

Ho circa 10 milioni di valori che devo inserire in un tipo di tabella di ricerca, quindi mi chiedevo quale sarebbe stato un elenco o un dict più efficiente ?

So che puoi fare qualcosa del genere per entrambi:

if something in dict_of_stuff:
    pass

e

if something in list_of_stuff:
    pass

Il mio pensiero è che il dict sarà più veloce ed efficiente.

Grazie per l'aiuto.

EDIT 1
Ulteriori informazioni su ciò che sto cercando di fare. Problema di Eulero 92 . Sto creando una tabella di ricerca per vedere se un valore calcolato è già stato calcolato.

EDIT 2
Efficienza per cercare.

EDIT 3
Non ci sono valori associati al valore ... quindi un set sarebbe migliore?


1
Efficienza in termini di cosa? Inserire? Consultare? Consumo di memoria? Stai verificando la pura esistenza del valore o ci sono metadati associati?
truppo,

Come nota a margine, non è necessario un elenco o un dict di 10 milioni per quel problema specifico ma molto più piccolo.
sfotiadis,

Risposte:


222

Velocità

Le ricerche negli elenchi sono O (n), le ricerche nei dizionari sono ammortizzate O (1), in relazione al numero di elementi nella struttura dei dati. Se non è necessario associare valori, utilizzare i set.

Memoria

Sia i dizionari che i set usano l'hashing e usano molta più memoria che solo per l'archiviazione degli oggetti. Secondo AM Kuchling in Beautiful Code , l'implementazione cerca di mantenere pieno l'hash 2/3, quindi potresti perdere un po 'di memoria.

Se non aggiungi nuove voci al volo (cosa che fai, in base alla tua domanda aggiornata), potrebbe essere utile ordinare l'elenco e utilizzare la ricerca binaria. Questo è O (log n), ed è probabilmente più lento per le stringhe, impossibile per gli oggetti che non hanno un ordinamento naturale.


6
Sì, ma è un'operazione una tantum se il contenuto non cambia mai. La ricerca binaria è O (log n).
Torsten Marek,

1
@John Fouhy: gli ints non sono memorizzati nella tabella hash, solo puntatori, cioè hai 40M per gli ints (beh, non proprio quando molti di loro sono piccoli) e 60M per la tabella hash. Sono d'accordo che al giorno d'oggi non è un grosso problema, ma vale la pena tenerlo a mente.
Torsten Marek,

2
Questa è una vecchia domanda, ma penso che l' ammortamento di O (1) potrebbe non essere vero per set / dicts molto grandi. Lo scenario peggiore secondo wiki.python.org/moin/TimeComplexity è O (n). Immagino che dipenda dall'implementazione dell'hashing interno in quale momento il tempo medio diverge da O (1) e inizia a convergere su O (n). Puoi aiutare le prestazioni di ricerca suddividendo gli insiemi globali in sezioni più piccole in base ad un attributo facilmente riconoscibile (come il valore della prima cifra, quindi della seconda, terza, ecc., Per tutto il tempo necessario per ottenere dimensioni ottimali degli insiemi) .
Nisan.H,

3
@TorstenMarek Questo mi confonde. Da questa pagina , la ricerca elenco è O (1) e la ricerca dict è O (n), che è l'opposto di quello che hai detto. Sto fraintendendo?
temporary_user_name

3
@Aerovistae Penso che tu abbia letto male le informazioni su quella pagina. Sotto l'elenco, vedo O (n) per "x in s" (ricerca). Mostra anche la ricerca set e dict come caso medio O (1).
Dennis,

45

Un dict è una tabella hash, quindi è molto veloce trovare le chiavi. Quindi tra dict e list, dict sarebbe più veloce. Ma se non hai un valore da associare, è ancora meglio usare un set. È una tabella hash, senza la parte "table".


EDIT: per la tua nuova domanda, SÌ, un set sarebbe meglio. Basta creare 2 set, uno per le sequenze terminate in 1 e l'altro per le sequenze terminate in 89. Ho risolto con successo questo problema utilizzando i set.



31

Ho fatto alcuni benchmarking e risulta che dict è più veloce di entrambi gli elenchi e impostato per grandi set di dati, eseguendo python 2.7.3 su una CPU i7 su Linux:

  • python -mtimeit -s 'd=range(10**7)' '5*10**6 in d'

    10 loop, meglio di 3: 64,2 msec per loop

  • python -mtimeit -s 'd=dict.fromkeys(range(10**7))' '5*10**6 in d'

    10000000 loop, meglio di 3: 0,0759 usec per loop

  • python -mtimeit -s 'from sets import Set; d=Set(range(10**7))' '5*10**6 in d'

    1000000 loop, meglio di 3: 0,262 usec per loop

Come puoi vedere, dict è considerevolmente più veloce della lista e circa 3 volte più veloce di quanto impostato. In alcune applicazioni potresti comunque voler scegliere set per la sua bellezza. E se i set di dati sono veramente piccoli (<1000 elementi) gli elenchi funzionano abbastanza bene.


Non dovrebbe essere esattamente il contrario? Elenco: 10 * 64,2 * 1000 = 642000 usec, dict: 10000000 * 0,0759 = 759000 usec e set: 1000000 * 0,262 = 262000 usec ... quindi i set sono i più veloci, seguiti dall'elenco e con dict come ultimo esempio. Oppure mi sfugge qualcosa?
Andzep

1
... ma la domanda per me qui è: cosa stanno misurando questi tempi? Non il tempo di accesso per un determinato elenco, dettare o impostare, ma molto di più, il tempo e i cicli per creare l'elenco, dettare, impostare e infine trovare e accedere a un valore. Quindi, questo ha a che fare con la domanda? ... È interessante però ...
Andzep

8
@andzep, ti sbagli, l' -sopzione è quella di impostare l' timeitambiente, cioè non conta nel tempo totale. L' -sopzione viene eseguita una sola volta. Su Python 3.3 ottengo questi risultati: gen (intervallo) -> 0,229 usec, elenco -> 157 msec, dict -> 0,0806 usec, set -> 0,0807 usec. Le prestazioni impostate e dettate sono le stesse. Tuttavia, Dict impiega un po 'più tempo a inizializzare rispetto a quanto impostato (tempo totale 13.580s v. 11.803s)
sleblanc,

1
perché non usare il set integrato? In realtà ottengo risultati molto peggiori con sets.Set () che con builtin set ()
Thomas Guyot-Sionnest

2
@ ThomasGuyot-Sionnest Il set integrato è stato introdotto in Python 2.4, quindi non sono sicuro del motivo per cui non l'ho usato nella soluzione proposta. Ottengo buone prestazioni python -mtimeit -s "d=set(range(10**7))" "5*10**6 in d"usando Python 3.6.0 (10000000 loop, meglio di 3: 0,0608 usec per loop), più o meno lo stesso del benchmark dict, quindi grazie per il tuo commento.
EriF89,

6

Vuoi un dict.

Per gli elenchi (non ordinati) in Python, l'operazione "in" richiede O (n) time --- non va bene quando si dispone di una grande quantità di dati. Un dict, d'altra parte, è una tabella hash, quindi puoi aspettarti un tempo di ricerca O (1).

Come altri hanno già notato, potresti scegliere un set (un tipo speciale di dict), invece, se hai solo chiavi anziché coppie chiave / valore.

Relazionato:

  • Wiki Python : informazioni sulla complessità temporale delle operazioni del contenitore Python.
  • SO : tempo di funzionamento del contenitore Python e complessità della memoria

1
Anche per gli elenchi ordinati, "in" è O (n).

2
Per un elenco collegato, sì --- ma gli "elenchi" in Python sono ciò che la maggior parte delle persone chiamerebbe vettori, che forniscono accesso indicizzato in O (1) e un'operazione di ricerca in O (registro n), se ordinati.
zweiterlinde,

Stai dicendo che l' inoperatore applicato a un elenco ordinato ha prestazioni migliori rispetto a quando applicato a un elenco non ordinato (per una ricerca di un valore casuale)? (Non penso che siano rilevanti internamente come vettori o nodi in un elenco collegato.)
martineau,

4

se i dati sono univoci set () sarà il più efficiente, ma di due-dict (che richiede anche unicità, oops :)


mi sono reso conto quando ho visto la mia risposta pubblicata%)
SilentGhost

2
@SilentGhost se la risposta è errata, perché non cancellarla? peccato per i voti positivi, ma ciò accade (beh, è accaduto )
Jean-François Fabre

3

Come una nuova serie di test per mostrare @ EriF89 è ancora giusto dopo tutti questi anni:

$ python -m timeit -s "l={k:k for k in xrange(5000)}"    "[i for i in xrange(10000) if i in l]"
1000 loops, best of 3: 1.84 msec per loop
$ python -m timeit -s "l=[k for k in xrange(5000)]"    "[i for i in xrange(10000) if i in l]"
10 loops, best of 3: 573 msec per loop
$ python -m timeit -s "l=tuple([k for k in xrange(5000)])"    "[i for i in xrange(10000) if i in l]"
10 loops, best of 3: 587 msec per loop
$ python -m timeit -s "l=set([k for k in xrange(5000)])"    "[i for i in xrange(10000) if i in l]"
1000 loops, best of 3: 1.88 msec per loop

Qui confrontiamo anche a tuple, che sono noti per essere più veloci di lists(e usano meno memoria) in alcuni casi d'uso. Nel caso della tabella di ricerca, iltuple premio non è migliore.

Sia il dictche ilset comportato molto bene. Questo fa emergere un punto interessante legato alla risposta di @SilentGhost sull'unicità: se l'OP ha valori 10M in un set di dati, ed è sconosciuto se ci sono duplicati in essi, varrebbe la pena mantenere un set / dict dei suoi elementi in parallelo con il set di dati effettivo e test per l'esistenza in tale set / dict. È possibile che i punti dati 10M abbiano solo 10 valori univoci, che è uno spazio molto più piccolo per la ricerca!

L'errore di SilentGhost sui dicts è in realtà illuminante perché si potrebbe usare un dict per correlare i dati duplicati (in valori) in un set non duplicato (chiavi), e quindi mantenere un oggetto dati per contenere tutti i dati, ma essere comunque veloce come una tabella di ricerca. Ad esempio, una chiave di dettatura potrebbe essere il valore che viene cercato e il valore potrebbe essere un elenco di indici in un elenco immaginario in cui si è verificato quel valore.

Ad esempio, se l'elenco dei dati di origine da cercare era l=[1,2,3,1,2,1,4], potrebbe essere ottimizzato sia per la ricerca che per la memoria sostituendolo con questo dict:

>>> from collections import defaultdict
>>> d = defaultdict(list)
>>> l=[1,2,3,1,2,1,4]
>>> for i, e in enumerate(l):
...     d[e].append(i)
>>> d
defaultdict(<class 'list'>, {1: [0, 3, 5], 2: [1, 4], 3: [2], 4: [6]})

Con questo detto, si può sapere:

  1. Se un valore era nel set di dati originale (ovvero 2 in drestituisce True)
  2. Dove il valore era di dati originale (cioè d[2]restituisce elenco degli indici in cui i dati è stato trovato nella lista dati originali: [1, 4])

Per il tuo ultimo paragrafo, mentre ha senso leggerlo, sarebbe bello (e probabilmente più facile da capire) vedere il codice reale che stai cercando di spiegare.
Kaiser

0

In realtà non è necessario memorizzare 10 milioni di valori nella tabella, quindi non è un grosso problema in entrambi i casi.

Suggerimento: pensa a quanto può essere grande il tuo risultato dopo la prima somma delle operazioni dei quadrati. Il risultato più grande possibile sarà molto più piccolo di 10 milioni ...

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.