Rimozione sicura di più chiavi da un dizionario


128

So che per rimuovere una voce, 'chiave' dal mio dizionario d, in modo sicuro, fai:

if d.has_key('key'):
    del d['key']

Tuttavia, devo rimuovere più voci dal dizionario in modo sicuro. Stavo pensando di definire le voci in una tupla poiché avrò bisogno di farlo più di una volta.

entitiesToREmove = ('a', 'b', 'c')
for x in entitiesToRemove:
    if d.has_key(x):
        del d[x]

Tuttavia, mi chiedevo se esiste un modo più intelligente per farlo?


3
Il tempo di recupero da un dizionario è quasi O (1) a causa dell'hashing. A meno che tu non stia rimuovendo una parte significativa delle voci, non credo che farai molto meglio.
ncmathsadist

1
La risposta di @mattbornski sembra più canonica, e anche succinta.
Ioannis Filippidis

2
StackOverflow ha parlato: key in dè più Pythonic di d.has_key(key) stackoverflow.com/questions/1323410/has-key-or-in
Michael Scheper

Se puoi risparmiare un po 'di memoria, puoi farlo for x in set(d) & entities_to_remove: del d[x]. Questo sarà probabilmente più efficiente solo se entities_to_removeè "grande".
DylanYoung

Risposte:


56

Perché non così:

entries = ('a', 'b', 'c')
the_dict = {'b': 'foo'}

def entries_to_remove(entries, the_dict):
    for key in entries:
        if key in the_dict:
            del the_dict[key]

Una versione più compatta è stata fornita da mattbornski usando dict.pop ()


14
Aggiungendo questo per le persone provenienti da un motore di ricerca. Se le chiavi sono note (quando la sicurezza non è un problema), è possibile eliminare più chiavi in ​​una riga come questadel dict['key1'], dict['key2'], dict['key3']
Tirtha R

A seconda del numero di chiavi che stai eliminando, potrebbe essere più efficiente utilizzare for key in set(the_dict) & entries:e ignorare il key in dicttest.
DylanYoung

236
d = {'some':'data'}
entriesToRemove = ('any', 'iterable')
for k in entriesToRemove:
    d.pop(k, None)

38
Questo. Questa è la scelta intelligente di Pythonista. dict.pop()elimina la necessità di test di esistenza chiave. Eccellente.
Cecil Curry

4
Per quello che vale, penso che .pop()sia cattivo e non pitonico, e preferirei la risposta accettata a questa.
Arne

5
Un numero sbalorditivo di persone sembra indifferente a questo :) Non mi dispiace la linea extra per il controllo dell'esistenza personalmente, ed è significativamente più leggibile a meno che tu non sappia già di pop (). D'altra parte, se stavi cercando di farlo in una comprensione o in un lambda in linea, questo trucco potrebbe essere di grande aiuto. Direi anche che è importante, secondo me, incontrare le persone dove si trovano. Non sono sicuro che "cattivo e non pitonico" darà alle persone che stanno leggendo queste risposte la guida pratica che stanno cercando.
mattbornski

5
C'è una ragione particolarmente buona per usarlo. Sebbene l'aggiunta di una riga aggiuntiva possa migliorare la "leggibilità" o la "chiarezza", aggiunge anche una ricerca extra al dizionario. Questo metodo è l'equivalente di rimozione del fare setdefault. Se implementato correttamente (e sono sicuro che lo sia), esegue solo una ricerca nella mappa hash che è dict, invece di due.
Mad Physicist

2
Personalmente, mi preoccuperei innanzitutto della correttezza e della manutenibilità, e della velocità solo se si è dimostrato che non è sufficientemente veloce. La differenza di velocità tra queste operazioni sarà insignificante quando si esegue lo zoom indietro a livello di applicazione. Potrebbe essere il caso che uno sia più veloce, ma mi aspetto che nell'uso del mondo reale non te ne accorgerai né ti preoccuperai, e se lo noti e ti preoccupi, ti sarà meglio riscrivere in qualcosa di più performante di Python.
mattbornski

90

Utilizzo di Dict Comprehensions

final_dict = {key: t[key] for key in t if key not in [key1, key2]}

dove key1 e key2 devono essere rimossi.

Nell'esempio seguente, le chiavi "b" e "c" devono essere rimosse e vengono mantenute in un elenco di chiavi.

>>> a
{'a': 1, 'c': 3, 'b': 2, 'd': 4}
>>> keys = ["b", "c"]
>>> print {key: a[key] for key in a if key not in keys}
{'a': 1, 'd': 4}
>>> 

4
nuovo dizionario? comprensione delle liste? Dovresti adattare la risposta alla persona che pone la domanda;)
Glaslos

6
Questa soluzione ha un grave calo delle prestazioni quando la variabile che contiene la ha un ulteriore utilizzo nel programma. In altre parole, un dict da cui sono state eliminate le chiavi è molto più efficiente di un dict appena creato con gli elementi conservati.
Apalala

14
per motivi di leggibilità, suggerisco {k: v per k, v in t.items () se k non in [key1, key2]}
Frederic Bazin

8
Ciò ha anche problemi di prestazioni quando l'elenco delle chiavi è troppo grande, come richiedono le ricerche O(n). L'intera operazione è O(mn), dove mè il numero di chiavi nel comando e nil numero di chiavi nell'elenco. Suggerisco invece di usare un set {key1, key2}, se possibile.
ldavid

4
Ad Apalala: puoi aiutarmi a capire perché c'è un colpo di prestazione?
Sean

21

una soluzione sta usando mape filterfunzioni

pitone 2

d={"a":1,"b":2,"c":3}
l=("a","b","d")
map(d.__delitem__, filter(d.__contains__,l))
print(d)

pitone 3

d={"a":1,"b":2,"c":3}
l=("a","b","d")
list(map(d.__delitem__, filter(d.__contains__,l)))
print(d)

ottieni:

{'c': 3}

Questo non funziona per me con Python 3.4:>>> d={"a":1,"b":2,"c":3} >>> l=("a","b","d") >>> map(d.__delitem__, filter(d.__contains__,l)) <map object at 0x10579b9e8> >>> print(d) {'a': 1, 'b': 2, 'c': 3}
Risadinha

@Risadinha list(map(d.__delitem__,filter(d.__contains__,l))).... nella funzione map di python 3.4 restituisce un iteratore
Jose Ricardo Bustos M.

4
o deque(map(...), maxlen=0)per evitare di creare un elenco di valori Nessuno; prima importazione confrom collections import deque
Jason

19

Se hai anche bisogno di recuperare i valori per le chiavi che stai rimuovendo, questo sarebbe un buon modo per farlo:

valuesRemoved = [d.pop(k, None) for k in entitiesToRemove]

Ovviamente potresti ancora farlo solo per la rimozione delle chiavi da d, ma creeresti inutilmente l'elenco di valori con la comprensione della lista. È anche un po 'poco chiaro usare una comprensione dell'elenco solo per l'effetto collaterale della funzione.


3
Oppure, se si desidera mantenere le voci eliminate come un dizionario: valuesRemoved = dict((k, d.pop(k, None)) for k in entitiesToRemove) e così via.
kindall

Puoi lasciare l'assegnazione a una variabile. In questo o in quel modo è la soluzione più breve e più pitonica e dovrebbe essere contrassegnata come risposta corretta IMHO.
Gerhard Hagerer

12

Ho trovato una soluzione con popemap

d = {'a': 'valueA', 'b': 'valueB', 'c': 'valueC', 'd': 'valueD'}
keys = ['a', 'b', 'c']
list(map(d.pop, keys))
print(d)

L'output di questo:

{'d': 'valueD'}

Ho risposto a questa domanda così tardi solo perché penso che sarà utile in futuro se qualcuno cerca la stessa cosa. E questo potrebbe aiutare.

Aggiornare

Il codice precedente genererà un errore se una chiave non esiste nel dict.

DICTIONARY = {'a': 'valueA', 'b': 'valueB', 'c': 'valueC', 'd': 'valueD'}
keys = ['a', 'l', 'c']

def remove_keys(key):
    try:
        DICTIONARY.pop(key, None)
    except:
        pass  # or do any action

list(map(remove_key, keys))
print(DICTIONARY)

produzione:

DICTIONARY = {'b': 'valueB', 'd': 'valueD'}

1
Questa risposta genererà un'eccezione se una chiave in keysnon esiste in d- dovresti prima filtrarla.
ingofreyer

@ingofreyer ha aggiornato il codice per la gestione delle eccezioni. Grazie per aver trovato questo problema. Penso che ora funzionerà. :)
Shubham Srivastava

Grazie, questo dovrebbe aiutare tutti a trovare questa risposta :-)
ingofreyer

La creazione di un elenco come sottoprodotto dell'utilizzo di map, rende tutto questo piuttosto lento, in realtà è meglio ripeterlo.
Charlie Clark,

4

Non ho alcun problema con nessuna delle risposte esistenti, ma sono rimasto sorpreso di non trovare questa soluzione:

keys_to_remove = ['a', 'b', 'c']
my_dict = {k: v for k, v in zip("a b c d e f g".split(' '), [0, 1, 2, 3, 4, 5, 6])}

for k in keys_to_remove:
    try:
        del my_dict[k]
    except KeyError:
        pass

assert my_dict == {'d': 3, 'e': 4, 'f': 5, 'g': 6}

Nota: mi sono imbattuto in questa domanda proveniente da qui . E la mia risposta è correlata a questa risposta .


3

Perchè no:

entriestoremove = (2,5,1)
for e in entriestoremove:
    if d.has_key(e):
        del d[e]

Non so cosa intendi per "modo più intelligente". Sicuramente ci sono altri modi, magari con la comprensione del dizionario:

entriestoremove = (2,5,1)
newdict = {x for x in d if x not in entriestoremove}

2

in linea

import functools

#: not key(c) in d
d = {"a": "avalue", "b": "bvalue", "d": "dvalue"}

entitiesToREmove = ('a', 'b', 'c')

#: python2
map(lambda x: functools.partial(d.pop, x, None)(), entitiesToREmove)

#: python3

list(map(lambda x: functools.partial(d.pop, x, None)(), entitiesToREmove))

print(d)
# output: {'d': 'dvalue'}

2

Alcuni test di temporizzazione per cpython 3 mostrano che un semplice ciclo for è il modo più veloce ed è abbastanza leggibile. Anche l'aggiunta di una funzione non causa molto overhead:

risultati timeit (10k iterazioni):

  • all(x.pop(v) for v in r) # 0.85
  • all(map(x.pop, r)) # 0.60
  • list(map(x.pop, r)) # 0.70
  • all(map(x.__delitem__, r)) # 0.44
  • del_all(x, r) # 0.40
  • <inline for loop>(x, r) # 0.35
def del_all(mapping, to_remove):
      """Remove list of elements from mapping."""
      for key in to_remove:
          del mapping[key]

Per piccole iterazioni, farlo "inline" era un po 'più veloce, a causa del sovraccarico della chiamata di funzione. Ma del_allè sicuro, riutilizzabile e più veloce di tutti i costrutti di mappatura e comprensione di Python.


0

Penso che usare il fatto che le chiavi possano essere trattate come un set sia il modo più carino se sei su Python 3:

def remove_keys(d, keys):
    to_remove = set(keys)
    filtered_keys = d.keys() - to_remove
    filtered_values = map(d.get, filtered_keys)
    return dict(zip(filtered_keys, filtered_values))

Esempio:

>>> remove_keys({'k1': 1, 'k3': 3}, ['k1', 'k2'])
{'k3': 3}

0

Sarebbe bello avere il pieno supporto per i metodi set per i dizionari (e non il pasticcio diabolico che stiamo ottenendo con Python 3.9) in modo da poter semplicemente "rimuovere" un set di chiavi. Tuttavia, fintanto che non è così e hai un dizionario di grandi dimensioni con potenzialmente un numero elevato di chiavi da rimuovere, potresti voler conoscere le prestazioni. Quindi, ho creato un codice che crea qualcosa di abbastanza grande per confronti significativi: una matrice 100.000 x 1000, quindi 10.000,00 elementi in totale.

from itertools import product
from time import perf_counter

# make a complete worksheet 100000 * 1000
start = perf_counter()
prod = product(range(1, 100000), range(1, 1000))
cells = {(x,y):x for x,y in prod}
print(len(cells))

print(f"Create time {perf_counter()-start:.2f}s")
clock = perf_counter()
# remove everything above row 50,000

keys = product(range(50000, 100000), range(1, 100))

# for x,y in keys:
#     del cells[x, y]

for n in map(cells.pop, keys):
    pass

print(len(cells))
stop = perf_counter()
print(f"Removal time {stop-clock:.2f}s")

10 milioni di elementi o più non sono insoliti in alcune impostazioni. Confrontando i due metodi sulla mia macchina locale vedo un leggero miglioramento durante l'utilizzo mape pop, presumibilmente a causa di un minor numero di chiamate di funzione, ma entrambi richiedono circa 2,5 secondi sulla mia macchina. Ma questo impallidisce rispetto al tempo necessario per creare il dizionario in primo luogo (55s), o includendo i controlli all'interno del ciclo. Se ciò è probabile, è meglio creare un insieme che sia un'intersezione delle chiavi del dizionario e del filtro:

keys = cells.keys() & keys

In sintesi: delè già fortemente ottimizzato, quindi non preoccuparti di usarlo.


-1

Sono in ritardo per questa discussione, ma per chiunque altro. Una soluzione potrebbe essere quella di creare un elenco di chiavi in ​​quanto tali.

k = ['a','b','c','d']

Quindi usa pop () in un elenco di comprensione, o ciclo for, per iterare sui tasti e visualizzarli uno alla volta come tali.

new_dictionary = [dictionary.pop(x, 'n/a') for x in k]

Il "n / a" è nel caso in cui la chiave non esiste, deve essere restituito un valore predefinito.


8
new_dictionaryassomiglia moltissimo a una lista;)
DylanYoung
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.