2009-08-05 41 views
30

Se ho un elenco di dizionari, dicono:Python: rimuovere dizionario dalla lista

[{'id': 1, 'name': 'paul'}, 
{'id': 2, 'name': 'john'}] 

e vorrei rimuovere il dizionario con id di 2 (o il nome John), qual è il modo più efficiente per andare su questo a livello di codice (vale a dire, non conosco l'indice della voce nella lista in modo che non possa essere semplicemente spuntato).

risposta

61
thelist[:] = [d for d in thelist if d.get('id') != 2] 

Edit: come alcuni dubbi sono stati espressi in un commento circa le prestazioni di questo codice (alcuni basati su fraintendendo le caratteristiche di prestazione di Python, un po 'nell'assumere al di là delle specifiche, dato che non v'è esattamente un dict nel lista con un valore di 2 per la chiave 'id'), vorrei offrire rassicurazione su questo punto.

Su una vecchia scatola di Linux, la misurazione di questo codice:

$ python -mtimeit -s"lod=[{'id':i, 'name':'nam%s'%i} for i in range(99)]; import random" "thelist=list(lod); random.shuffle(thelist); thelist[:] = [d for d in thelist if d.get('id') != 2]" 
10000 loops, best of 3: 82.3 usec per loop 

di cui circa 57 microsecondi per la random.shuffle (necessario per garantire che l'elemento da rimuovere non è sempre nello stesso punto ;-) e 0,65 microsecondi per la copia iniziale (chi si preoccupa dell'impatto sulle prestazioni delle copie superficiali delle liste Python è ovviamente fuori a pranzo ;-), necessario per evitare di alterare l'elenco originale nel ciclo (quindi ogni parte del ciclo ha qualcosa da Elimina;-).

Quando si sa che c'è esattamente un elemento da rimuovere, è possibile individuare e rimuovere ancora più rapidamente:

$ python -mtimeit -s"lod=[{'id':i, 'name':'nam%s'%i} for i in range(99)]; import random" "thelist=list(lod); random.shuffle(thelist); where=(i for i,d in enumerate(thelist) if d.get('id')==2).next(); del thelist[where]" 
10000 loops, best of 3: 72.8 usec per loop 

(utilizzare il comando incorporato next piuttosto che il metodo .next se siete su Python 2.6 o superiore, ovviamente) - ma questo codice si rompe se il numero di dict che soddisfano la condizione di rimozione non è esattamente uno. Generalizzando questo, abbiamo:

$ python -mtimeit -s"lod=[{'id':i, 'name':'nam%s'%i} for i in range(33)]*3; import random" "thelist=list(lod); where=[i for i,d in enumerate(thelist) if d.get('id')==2]; where.reverse()" "for i in where: del thelist[i]" 
10000 loops, best of 3: 23.7 usec per loop 

dove il rimescolamento può essere rimosso perché ci sono già tre dicts equidistanti da rimuovere, come sappiamo. E il listcomp, invariati, tariffe e:

$ python -mtimeit -s"lod=[{'id':i, 'name':'nam%s'%i} for i in range(33)]*3; import random" "thelist=list(lod); thelist[:] = [d for d in thelist if d.get('id') != 2]" 
10000 loops, best of 3: 23.8 usec per loop 

totalmente testa a testa, con anche solo 3 elementi di 99 da rimuovere. Con gli elenchi più lunghi e più ripetizioni, questo vale ancora di più, naturalmente:

$ python -mtimeit -s"lod=[{'id':i, 'name':'nam%s'%i} for i in range(33)]*133; import random" "thelist=list(lod); where=[i for i,d in enumerate(thelist) if d.get('id')==2]; where.reverse()" "for i in where: del thelist[i]" 
1000 loops, best of 3: 1.11 msec per loop 
$ python -mtimeit -s"lod=[{'id':i, 'name':'nam%s'%i} for i in range(33)]*133; import random" "thelist=list(lod); thelist[:] = [d for d in thelist if d.get('id') != 2]" 
1000 loops, best of 3: 998 usec per loop 

Tutto sommato, non è, ovviamente, vale la distribuzione la sottigliezza di fare e invertire l'elenco degli indici di rimuovere, contro la lista perfettamente semplice ed evidente comprensione, per guadagnare forse 100 nanosecondi in un piccolo caso - e perdere 113 microsecondi in uno più grande ;-). Evitare o criticare soluzioni semplici, dirette e perfettamente adeguate alle prestazioni (come la comprensione delle liste per questa classe generale di problemi "rimuovere alcuni elementi da una lista") è un esempio particolarmente sgradevole della famosa tesi di Knuth e Hoare secondo cui "l'ottimizzazione prematura è la radice di tutti i mali in programmazione "-)

+1

Due motivi per cui questo non funziona: copia l'intera lista e attraversa l'intera lista anche se il dizionario contenente id 2 è il primo elemento. – Imagist

+12

@imagist, è comunque il più veloce - MISURARLO, per carità, non solo ASSUMI di sapere di cosa stai parlando, esp. quando ovviamente non lo fai ;-), * SOPRATTUTTO * quando l'elemento da rimuovere è il primo (evita di spostare ogni altro oggetto). E non c'è alcuna indicazione nella domanda originale che ogni dict nella lista DEVE sempre avere un valore diverso corrispondente a 'id'. –

+0

Hmmmm. Non male. Ci sono due approcci: creare un nuovo elenco con alcuni elementi filtrati o modificare l'elenco esistente per rimuovere alcuni elementi. Questo è solo il primo approccio. E per quanto riguarda ciò, non c'è nulla da dire che un dizionario con id = 2 non apparirà più di una volta nella lista. È una lista - non c'è garanzia di unicità. E l'OP non ha suggerito questa limitazione. – hughdbrown

0

si può provare la seguente:!

a = [{'id': 1, 'name': 'paul'}, 
    {'id': 2, 'name': 'john'}] 

for e in range(len(a) - 1, -1, -1): 
    if a[e]['id'] == 2: 
     a.pop(e) 

se non si può pop fin dall'inizio - pop dalla fine, non si rovina la per ciclo continuo.

+0

Intendi "intervallo (len (a) - 1, -1, -1)", non "intervallo (len (a) - 1, 0, -1)". Questo non include il primo elemento della lista. Ho sentito dire che il contrario() è preferito al giorno d'oggi. Vedi il mio codice qui sotto. – hughdbrown

+0

Ecco cosa volevo arrivare: >>> a = lista (range (5)) >>> a [, 4 0, 1, 2, 3] >>> gamma (len (a) - 1, -1, -1) [4, 3, 2, 1, 0] >>> intervallo (len (a) - 1, 0, -1) [4, 3, 2, 1] Aspetta solo il commento di manomissione ... – hughdbrown

4

Ecco un modo per farlo con una lista di comprensione (supponendo che il nome 'foo' l'elenco):

[x for x in foo if not (2 == x.get('id'))] 

sostituto 'john' == x.get('name') o qualsiasi altra cosa a seconda dei casi.

filter funziona anche:

foo.filter(lambda x: x.get('id')!=2, foo)

E se volete un generatore è possibile utilizzare itertools:

itertools.ifilter(lambda x: x.get('id')!=2, foo)

Tuttavia, come di Python 3, filter restituirà un iteratore comunque quindi la comprensione delle liste è davvero la scelta migliore, come suggeriva Alex.

+0

anche, .get è meglio di [] qui, in quanto non si interrompe se alcuni dict nella lista NON hanno una voce per la chiave 'id'. –

+0

Buon suggerimento - risposta modificata di conseguenza. –

0

Si potrebbe provare qualcosa lungo le seguenti linee:

def destructively_remove_if(predicate, list): 
     for k in xrange(len(list)): 
      if predicate(list[k]): 
       del list[k] 
       break 
     return list 

    list = [ 
     { 'id': 1, 'name': 'John' }, 
     { 'id': 2, 'name': 'Karl' }, 
     { 'id': 3, 'name': 'Desdemona' } 
    ] 

    print "Before:", list 
    destructively_remove_if(lambda p: p["id"] == 2, list) 
    print "After:", list 

A meno che non si costruisce qualcosa di simile ad un indice sopra i vostri dati, ho non credo che si può fare meglio che fare una forza bruta " tabella scansione "su tutta la lista. Se i tuoi dati sono ordinati per la chiave che stai usando, puoi usare il modulo bisect per per trovare l'oggetto che stai cercando un po 'più veloce.

6

Questo non è propriamente un anwser (come penso che tu ne abbia già alcuni abbastanza buoni), ma ... hai considerato di avere un dizionario di <id>:<name> invece di un elenco di dizionari?

+1

+1: "Se è difficile, stai sbagliando." Se vuoi rimuovere le cose con un attributo, usa un dizionario, digitato dall'attributo. Molto più semplice –

+1

... se non ti interessa affatto preservare l'ordine degli articoli, non vuoi mai rimuovere le cose con un altro attributo, sei felice di non consentire mai duplicati riguardo a quell'attributo, ecc. Ecc. molte restrizioni al di sopra e al di là di ogni specifica espressa dall'OP, per rendere ragionevole questo suggerimento ;-). –

+0

Se avessi dovuto dare per scontate tutte quelle specifiche, avrei detto "usa un database" xD – fortran

2
# assume ls contains your list 
for i in range(len(ls)): 
    if ls[i]['id'] == 2: 
     del ls[i] 
     break 

sarà probabilmente più veloce rispetto ai metodi di lista, in media, perché non attraversa l'intera lista se trova l'oggetto in questione nella fase iniziale.

+0

sollevere 'KeyError' se dict non ha' id'. e non è quello che l'OP chiedeva. – SilentGhost

+0

@Imagist +1 Questo era esattamente quello che stavo cercando. Nota per @SilentGhost: potresti usare una chiave diversa, diversa da 'id', se vuoi scegliere un altro valore, ad esempio:' if ls [i] ['name'] == 'john': 'corrisponderebbe e rimuovi quel dizionario. – natureminded

Problemi correlati