Controlla se una determinata chiave esiste già in un dizionario e incrementala


295

Dato un dizionario, come posso sapere se una determinata chiave in quel dizionario è già stata impostata su un valore diverso da Nessuno?

Cioè, voglio fare questo:

my_dict = {}

if (my_dict[key] != None):
  my_dict[key] = 1
else:
  my_dict[key] += 1

Vale a dire, voglio incrementare il valore se ce n'è già uno lì, o impostarlo su 1 altrimenti.


11
Piccolo codice nitpick: il codice imposta my_dict [chiave] su 1 se c'è già qualcosa lì, e lo incrementa se non lo è. Penso che tu voglia ==, non! =.
QuantumFool,

Risposte:


331

Stai cercando collections.defaultdict(disponibile per Python 2.5+). Questo

from collections import defaultdict

my_dict = defaultdict(int)
my_dict[key] += 1

farà quello che vuoi.

Per regolare Python dicts, se non v'è alcun valore per una data chiave, si non ottiene Nonequando si accede alla dict - una KeyErrorsarà sollevata. Quindi, se si desidera utilizzare un normale dict, invece del codice si dovrebbe usare

if key in my_dict:
    my_dict[key] += 1
else:
    my_dict[key] = 1

8
Secondo il suo esempio, dovrebbe essere sufficiente impostare "defaultdict (lambda: 0)" e saltare l'intera clausola "if".
Deestan,

Funziona, ma confonde chiavi e valori (rendendolo un po 'strano da leggere). 'some_value' dovrebbe essere 'some_key'
mikemaccana,

@nailer: risolto, grazie. Inizialmente avevo usato 'some_value' poiché quello è il nome della variabile nella domanda, ma sono d'accordo che ora è più chiaro.
dF.

20
... o per i normali dict, puoi farlo my_dict[key] = my_dict.get(key, 0) + 1.
minmaxavg,

Come estenderlo ai dizionari nidificati? dict [key1] [key2] + = 1?
Pablo Ruiz Ruiz,

301

Preferisco farlo in una riga di codice.

my_dict = {}

my_dict [some_key] = my_dict.get (some_key, 0) + 1

I dizionari hanno una funzione, get, che accetta due parametri: la chiave desiderata e un valore predefinito se non esiste. Preferisco questo metodo a defaultdict poiché vuoi solo gestire il caso in cui la chiave non esiste in questa riga di codice, non ovunque.


1
@AndrewWilkinson my bad. Non ho letto la tua risposta come avrei dovuto.
Masaers,

59

Personalmente mi piace usare setdefault()

my_dict = {}

my_dict.setdefault(some_key, 0)
my_dict[some_key] += 1

setdefaultè magnifico. Non altera il valore se ne è già impostato uno some_key. Ad esempio, d={1:2}; d.setdefault(1, 0)non disturba il valore di d[1].
wsaleem,

49

Hai bisogno del key in dictlinguaggio per quello.

if key in my_dict and not (my_dict[key] is None):
  # do something
else:
  # do something else

Tuttavia, dovresti probabilmente considerare l'utilizzo defaultdict(come suggerito dF).


1
Si noti che in almeno 2.6 has_key () è stato deprecato a favore della chiave in d. Penso che sia stato così anche nella 2.5.
David Locke,

Si noti che si può scrivere my_dict[key] is not None, il che è più chiaro (almeno IMHO)
brandizzi il

@brandizzi - d'accordo,if key in my_dict and my_dict[key]:
Rob Grant

18

Per rispondere alla domanda " come posso sapere se un determinato indice in quel dict è già stato impostato su un valore diverso da Nessuno ", preferirei questo:

try:
  nonNone = my_dict[key] is not None
except KeyError:
  nonNone = False

Ciò è conforme al concetto già invocato di EAFP (più facile chiedere perdono quindi permesso). Evita anche la ricerca di chiavi duplicate nel dizionario come sarebbe key in my_dict and my_dict[key] is not Noneinteressante se la ricerca fosse costosa.

Per l'effettivo problema che hai posto, ovvero aumentare un int se esiste o impostarlo su un valore predefinito in caso contrario, ti consiglio anche di

my_dict[key] = my_dict.get(key, default) + 1

come nella risposta di Andrew Wilkinson.

Esiste una terza soluzione se si memorizzano oggetti modificabili nel dizionario. Un esempio comune di ciò è una multimappa , in cui è memorizzato un elenco di elementi per le chiavi. In tal caso, puoi utilizzare:

my_dict.setdefault(key, []).append(item)

Se nel dizionario non esiste un valore per chiave, il metodo setdefault lo imposterà sul secondo parametro di setdefault. Si comporta come un my_dict standard [chiave], restituendo il valore per la chiave (che potrebbe essere il valore appena impostato).


ciò che sembra davvero Pythonic (a un estraneo come me) è che qualsiasi domanda ha almeno 3 risposte valide :)
davka,

@davka: Bene, i tre casi d'uso sono quasi uguali, ma diversi: a) scoprire se nel dizionario è presente un elemento diverso da Nessuno b) recuperare un valore dal dizionario o utilizzare un valore predefinito se il valore non esiste c) recuperare un valore dal dizionario e memorizzare un valore predefinito se il valore non esiste ancora.
nd.

Lo so :) questa non è una critica, sono solo divertito da questo fatto
davka

In un commento alla risposta di @ ryeguy, Stuart Woodward suggerisce "l'overhead nella gestione delle eccezioni nelle lingue è sempre un ordine di grandezza maggiore della ricerca nella tabella hash che determina se l'elemento esiste o meno nel dizionario", mentre stai dicendo "It evita anche la ricerca di chiavi duplicate nel dizionario ... se la ricerca è costosa "- qualcuno ha qualche misura di dove la gestione delle eccezioni è più veloce o più lenta di una ricerca a doppia chiave?
Michael Firth,

1
@MichaelFirth Ho fatto una rapida ricerca dell'overhead delle eccezioni di Python: stackoverflow.com/questions/2522005/… è più lento, ma non di molto. Tieni presente che il concetto di alto livello di lancio di un'eccezione è gestito in modo molto diverso in diverse lingue e non puoi generalizzare i pro ei contro. Pertanto, mentre "Exceptions ha un overhead 10x" potrebbe essere corretto per Java, non è per Python (o Swift o altri).
nd.

13

Concordato con cgoldberg. Come lo faccio è:

try:
    dict[key] += 1
except KeyError:
    dict[key] = 1

Quindi, fallo come sopra o usa un dict predefinito come altri hanno suggerito. Non usare if dichiarazioni. Questo non è Pythonic.


8
Come sono se le istruzioni non sono Pythonic?
Adam Parkin

2
Penso che questo sia un caso in cui l'EAFP di Python non è il modo migliore. Il tuo esempio sopra ha un codice duplicato; cosa succede se un giorno vogliamo +=2o -=1? Devi ricordare di cambiare entrambe le linee. Potrebbe sembrare una cosa banale ora, ma quelli sono il tipo di stupidi piccoli bug "banali" che possono tornare a morderti.
Cam Jackson

3
Sembra bello e funziona bene, ma di solito evito di farlo in questo modo perché ho pensato che l'overhead nella gestione delle eccezioni nelle lingue è sempre un ordine di grandezza maggiore della ricerca della tabella hash che determina se l'elemento esiste o meno nel dizionario.
Stuart Woodward,

11

Come puoi vedere dalle molte risposte, ci sono diverse soluzioni. Non è stata ancora menzionata un'istanza di LBYL (prima di saltare), il metodo has_key ():

my_dict = {}

def add (key):
    if my_dict.has_key(key):
        my_dict[key] += 1
    else:
        my_dict[key] = 1

if __name__ == '__main__':
    add("foo")
    add("bar")
    add("foo")
    print my_dict

6
has_key () è più lento dell'operatore 'in' ed è meno leggibile.
Abgan,

9
... ed è stato deprecato in Python 2.6 e rimosso in Python 3.
Tim Pietzcker

7

Il modo in cui stai cercando di farlo si chiama LBYL (guarda prima di saltare), poiché stai verificando le condizioni prima di provare ad aumentare il tuo valore.

L'altro approccio si chiama EAFP (è più facile chiedere perdono e poi l'autorizzazione). In tal caso, dovresti semplicemente provare l'operazione (incrementare il valore). Se fallisce, prendi l'eccezione e imposti il ​​valore su 1. Questo è un modo leggermente più Pythonic per farlo (IMO).

http://mail.python.org/pipermail/python-list/2003-May/205182.html


5

Un po 'in ritardo, ma dovrebbe funzionare.

my_dict = {}
my_dict[key] = my_dict[key] + 1 if key in my_dict else 1

Wow, come programmatore Java, questo è un costrutto piuttosto pazzo. Sembra un operatore ternario stranamente ordinato?
forresthopkinsa,

5

Questo non risponde direttamente alla domanda, ma a me sembra che tu possa desiderare la funzionalità delle collezioni .

from collections import Counter

to_count = ["foo", "foo", "bar", "baz", "foo", "bar"]

count = Counter(to_count)

print(count)

print("acts just like the desired dictionary:")
print("bar occurs {} times".format(count["bar"]))

print("any item that does not occur in the list is set to 0:")
print("dog occurs {} times".format(count["dog"]))

print("can iterate over items from most frequent to least:")
for item, times in count.most_common():
    print("{} occurs {} times".format(item, times))

Ciò comporta l'output

Counter({'foo': 3, 'bar': 2, 'baz': 1})
acts just like the desired dictionary:
bar occurs 2 times
any item that does not occur in the list is set to 0:
dog occurs 0 times
can iterate over items from most frequent to least:
foo occurs 3 times
bar occurs 2 times
baz occurs 1 times

Il contatore funziona proprio come defaultdict(int)con alcune funzionalità extra, quindi funzionerebbe perfettamente quando si tratta esclusivamente di numeri interi ma non si mostra alcun comportamento rilevante.
Tadhg McDonald-Jensen,

4

Ecco una riga che ho escogitato di recente per risolvere questo problema. Si basa sul metodo del dizionario setdefault :

my_dict = {}
my_dict[key] = my_dict.setdefault(key, 0) + 1

0

Lo stavo cercando, non l'ho trovato sul web, poi ho tentato la fortuna con Try / Error e l'ho trovato

my_dict = {}

if my_dict.__contains__(some_key):
  my_dict[some_key] += 1
else:
  my_dict[some_key] = 1

1
Non dovresti usare __contains__un codice di produzione. btw. __contains__è lo stesso che usare is.
user1767754

1
my_dict.__contains__(some_key)è equivalente a some_key in my_dict, non è un sovraccarico per l' inoperatore nois
Tadhg McDonald-Jensen,
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.