2009-08-12 35 views
10

Sono nel tratto finale di un progetto su cui ho lavorato. Tutto funziona senza intoppi ma ho un collo di bottiglia che ho problemi a lavorare in giro.Python: rimuovere molti elementi da un elenco

Ho una lista di tuple. L'elenco ha una durata che va da 40.000 a 1.000.000 di record. Ora ho un dizionario in cui ognuno (valore, chiave) è una tupla nella lista.

Quindi, potrei avere

myList = [(20000, 11), (16000, 4), (14000, 9)...] 
myDict = {11:20000, 9:14000, ...} 

Voglio rimuovere ciascuno (v, k) tuple dalla lista.

Attualmente sto facendo:

for k, v in myDict.iteritems(): 
    myList.remove((v, k)) 

Rimozione 838 tuple dalla lista che contiene 20.000 tuple impiega da 3 - 4 secondi. Molto probabilmente rimuoverò più di 10.000 tuple da una lista di 1.000.000, quindi ho bisogno che questo sia più veloce.

C'è un modo migliore per farlo?

Posso fornire il codice utilizzato per testare, oltre ai dati in salamoia dall'applicazione effettiva se necessario.

risposta

19

Dovrete misurare, ma posso immaginare che questo è più performante:

myList = filter(lambda x: myDict.get(x[1], None) != x[0], myList) 

perché la ricerca avviene nel dizionario, che è più adatto per questo genere di cose. Si noti, tuttavia, che ciò creerà una nuova lista prima di rimuovere quella precedente; quindi c'è un compromesso di memoria. Se questo è un problema, ripensa il tuo tipo di contenitore come suggerito da jkp potrebbe essere in ordine.

Modifica: fai attenzione, però, se None è effettivamente nel tuo elenco, dovresti utilizzare un "segnaposto" diverso.

+1

Wow. Questo ha portato il mio tempo di prova da 3,2 secondi a 0,025 ... Penso che potremmo avere un vincitore - almeno fino a quando Alex Martelli non suonerà :) – sberry

+2

Potrei vivere con il fatto di essere secondo a lui :-) – balpha

+0

@ sberry2A: Se sei misurando 25 ms, il tempo reale del muro potrebbe anche essere più piccolo di quello - potrebbe essere la risoluzione del timer del tuo sistema operativo "arrotondare" fino a 25 ms. Prova ad eseguire la media di 1000 esecuzioni, ad esempio. –

2

Il problema mi sembra il fatto che si sta utilizzando un list come contenitore da cui si sta tentando di rimuovere ed è un tipo completamente non ordinato. Quindi, per trovare ogni elemento nella lista è un'operazione lineare (O(n)), deve iterare sull'intero elenco fino a quando non trova una corrispondenza.

Se è possibile scambiare lo list per un altro contenitore (set?) Che utilizza uno hash() di ciascun articolo per ordinarli, allora ogni corrispondenza potrebbe essere eseguita molto più rapidamente.

Il seguente codice mostra come si potrebbe fare questo utilizzando una combinazione di idee offerti da me e Nick su questo thread:

list_set = set(original_list) 
dict_set = set(zip(original_dict.values(), original_dict.keys())) 
difference_set = list(list_set - dict_set) 
final_list = [] 
for item in original_list: 
    if item in difference_set: 
     final_list.append(item) 
+0

Giusto, tuttavia, ho bisogno che vengano ordinati. All'inizio stavo usando un dizionario per memorizzare gli elementi in myList come v: k per ciascuno (k, v) in myList sopra.Ma poiché ho bisogno che vengano ordinati, dovevo ordinare le k, v coppie del dizionario ogni volta che aggiungevo, cambiavo i dati. – sberry

+0

OK, se prendi la risposta fornita da Nick Lewis, una volta che hai il set di elementi da conservare, puoi fare quanto segue: scorrere l'elenco originale e interrogare il set per l'appartenenza a ciascun elemento: se l'elemento è nel set, aggiungilo alla tua lista finale. Finirai con una lista ordinata degli oggetti che desideri. – jkp

5

Ogni volta che si chiama myList.remove, Python ha per eseguire la scansione su tutta la lista per la ricerca per quell'articolo e rimuoverlo. Nel peggiore dei casi, ogni oggetto che cerchi sarà alla fine della lista ogni volta.

Hai provato a fare l'operazione "inversa" di:

newMyList = [(v,k) for (v,k) in myList if not k in myDict] 

ma sono davvero non so come bene che sarebbe in scala, sia, dal momento che si sarebbe fare una copia della lista originale - potrebbe potenzialmente essere un sacco di utilizzo della memoria lì.

Probabilmente la migliore alternativa qui è aspettare Alex Martelli per postare un approccio incredibilmente intuitivo, semplice ed efficiente.

+0

Questo è molto più veloce del mio codice originale. Tuttavia, è circa 3-4 volte più lento delle risposte di Balpha e di Nick Lewis. – sberry

2
[(i, j) for i, j in myList if myDict.get(j) != i] 
+0

È lo stesso di balpha ma usa una list comprehension invece di filter(). – hughdbrown

+0

Questo dovrebbe essere lo stesso di Mark Rushakoff. – hughdbrown

+0

non è, caro. – SilentGhost

2

provare qualcosa di simile:

myListSet = set(myList) 
myDictSet = set(zip(myDict.values(), myDict.keys())) 
myList = list(myListSet - myDictSet) 

Questo convertirà myList ad un set, cambierà le chiavi/valori in myDict e metterle in un set, per poi calcolare la differenza, turno torna in un elenco e assegnarlo a myList. :)

+0

I tempi qui sono molto, molto vicini a quelli ottenuti con il suggerimento di balpha. Sono +/- 4 millisecondi. È potenzialmente migliore per le liste più grandi? – sberry

+0

balpha probabilmente consuma meno memoria. – recursive

0
[i for i in myList if i not in list(zip(myDict.values(), myDict.keys()))] 
+2

Hai provato questo? La mia lettura del tuo codice è che stai facendo una ricerca lineare per una tupla in una lista, quindi questo è O (n^2) per l'intera operazione. Ogni singola soluzione up-votata avrà prestazioni migliori di questa. – hughdbrown

+0

Questo valuta anche l'espressione sulla destra per ogni oggetto - passando attraverso il 'dict' ogni volta. – agf

0

Un elenco contenente un milione di tuple da 2 non è grande sulla maggior parte delle macchine che eseguono Python. Tuttavia, se si deve assolutamente fare la rimozione in situ, ecco un modo pulito di fare in modo corretto:

def filter_by_dict(my_list, my_dict): 
    sentinel = object() 
    for i in xrange(len(my_list) - 1, -1, -1): 
     key = my_list[i][1] 
     if my_dict.get(key, sentinel) is not sentinel: 
      del my_list[i] 

Aggiornamento In realtà ciascuno del costa O (n) mischiare i puntatori della lista verso il basso con memmove di C(), quindi se ci sono d dels, è O(n*d) non O(n**2). Nota che (1) l'OP suggerisce che d approssimativamente == 0.01 * n e (2) lo sforzo O(n*d) sta copiando un puntatore in un'altra posizione nella memoria ... quindi questo metodo potrebbe in effetti essere un po 'più veloce di quanto una rapida occhiata potrebbe indicare. Benchmark, chiunque?

Cosa hai intenzione di fare con la lista dopo il hai rimosso gli elementi che sono nel dict? È possibile portare il filtro dict sul passo successivo?

+0

Se lo farai, potresti anche generare l'elenco delle chiavi da eliminare e eseguirle in ordine inverso. Sembra un po 'più idiomatico per me. delete_me = [i per i, v in enumerate (my_list) se v non in my_dict]; per i in reverse (delete_me): del my_list [i]; Inoltre, Beazley afferma che l'in-operatore è più veloce del metodo dict.get, FWIW. – hughdbrown

+0

Argh. delete_me = [i per i, v in enumerate (my_list) se v [1] non in my_dict]; – hughdbrown

+0

(1) Se farlo in tre passaggi (incluso costruire una lista temporanea e invertirla) è "idiomatico", quindi "idiomatico" è sbagliato. (2) l'uso di dict.get ha la stessa semantica dell'uso dell'OP di list.remove: entrambi k & v devono corrispondere tra elenco e dict. L'OP non ha indicato diversamente. (3) In ogni caso intendevi "v [1] nel mio dict" non "v [1] non in dict" - il dict contiene quelli da cancellare. Optibeazation molto prematura ;-) –

9

Per rimuovere circa 10.000 tuple da un elenco di circa 1.000.000, se i valori sono hashable, l'approccio più veloce dovrebbe essere:

totoss = set((v,k) for (k,v) in myDict.iteritems()) 
myList[:] = [x for x in myList if x not in totoss] 

La preparazione del set è un piccolo costo una tantum, Wich salva facendo tuple decompressione e reimballaggio, o tuple indexing, un sacco di volte. Assignign a myList[:] invece di assegnare ad myList è anche semanticamente importante (nel caso in cui non ci sono altri riferimenti a myList in giro, non è sufficiente per associare nuovamente solo il nome - si vuole veramente associare nuovamente i contenuti -!).

Non ho i dati di test in giro per fare la misurazione del tempo, ahimè !, ma, fammi sapere come funziona sui nostri dati di test!

Se i valori non sono hashable (ad esempio, sono sotto-liste, per esempio), il più veloce è probabilmente:

sentinel = object() 
myList[:] = [x for x in myList if myDict.get(x[0], sentinel) != x[1]] 

o forse (non dovrebbe fare una grande differenza in entrambi i casi, ma ho il sospetto il precedente è meglio - indicizzazione è più economico di disimballaggio e ricondizionamento):

sentinel = object() 
myList[:] = [(a,b) for (a,b) in myList if myDict.get(a, sentinel) != b] 

In questi due varianti del linguaggio sentinella è utilizzato per allontanare contro valori di None (che non è un problema per il set-based preferito approccio - se i valori sono lavabili!) come sta andando a essere molto più economico di if a not in myDict or myDict[a] != b (che richiede due indicizzazioni in myDict).

+1

Penso che non vedevamo l'ora di vedere la tua risposta. (Nota: un errore di battitura nella tua prima riga di codice ('i')) – Anon

+1

tx per l'individuazione di errori di battitura, risolvendolo ora –

Problemi correlati