Vérifier si une clé donnée existe déjà dans un dictionnaire et l'incrémenter

295

Étant donné un dictionnaire, comment puis-je savoir si une clé donnée dans ce dictionnaire a déjà été définie sur une valeur non nulle?

C'est-à-dire que je veux faire ceci:

my_dict = {}

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

C'est-à-dire que je veux incrémenter la valeur s'il y en a déjà une, ou la mettre à 1 sinon.

Ben
la source
11
Petit code nitpick: le code définit my_dict [key] à 1 s'il y a déjà quelque chose et l'incrémente s'il n'y en a pas. Je pense que vous voulez ==, pas! =.
QuantumFool

Réponses:

331

Vous recherchez collections.defaultdict(disponible pour Python 2.5+). Ce

from collections import defaultdict

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

fera ce que vous voulez.

Pour Python régulière dicts, s'il n'y a pas de valeur pour une clé donnée, vous n'obtenez lorsque vous accédez au dict - un ressusciteront. Donc, si vous souhaitez utiliser un régulier , au lieu de votre code, vous utiliseriezNoneKeyErrordict

if key in my_dict:
    my_dict[key] += 1
else:
    my_dict[key] = 1
dF.
la source
8
Selon son exemple, il devrait suffire de définir "defaultdict (lambda: 0)" et d'ignorer l'intégralité de la clause "if".
Deestan
Cela fonctionne, mais confond les clés et les valeurs (ce qui rend la lecture quelque peu étrange). 'some_value' devrait être 'some_key'
mikemaccana
@nailer: corrigé, merci. J'avais initialement utilisé 'some_value' puisque c'est le nom de la variable dans la question, mais je suis d'accord que c'est plus clair maintenant.
dF.
20
... ou pour les habitués dict, vous pouvez le faire my_dict[key] = my_dict.get(key, 0) + 1.
minmaxavg
Comment étendre cela aux dictionnaires imbriqués? dict [key1] [key2] + = 1?
Pablo Ruiz Ruiz
301

Je préfère le faire dans une seule ligne de code.

my_dict = {}

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

Les dictionnaires ont une fonction, get, qui prend deux paramètres - la clé que vous voulez, et une valeur par défaut si elle n'existe pas. Je préfère cette méthode à defaultdict car vous ne voulez gérer que le cas où la clé n'existe pas dans cette seule ligne de code, pas partout.

Andrew Wilkinson
la source
1
@AndrewWilkinson mon mauvais. Je n'ai pas lu votre réponse aussi complètement que j'aurais dû.
masaers
59

J'aime personnellement utiliser setdefault()

my_dict = {}

my_dict.setdefault(some_key, 0)
my_dict[some_key] += 1
kichik
la source
setdefaultest génial. Il ne modifie pas la valeur si une est déjà définie pour some_key. Par exemple, d={1:2}; d.setdefault(1, 0)ne perturbe pas la valeur de d[1].
wsaleem
49

Vous avez besoin de l' key in dictidiome pour cela.

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

Cependant, vous devriez probablement envisager d'utiliser defaultdict(comme suggéré par dF).

Eli Bendersky
la source
1
Veuillez noter que dans au moins 2.6 has_key () a été supprimé en faveur de la clé en d. Je pense que c'était aussi ainsi en 2.5.
David Locke
Notez que l'on peut écrire my_dict[key] is not None, ce qui est plus clair (à
mon humble avis
@brandizzi - d'accord,if key in my_dict and my_dict[key]:
Rob Grant
18

Pour répondre à la question " comment puis-je savoir si un index donné dans ce dict a déjà été défini sur une valeur non nulle ", je préférerais ceci:

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

Cela est conforme au concept déjà invoqué d'EAFP (plus facile de demander pardon puis permission). Cela évite également la recherche de clé en double dans le dictionnaire comme ce serait le cas dans key in my_dict and my_dict[key] is not Nonece qui est intéressant si la recherche est coûteuse.

Pour le problème réel que vous avez posé, c'est-à-dire incrémenter un int s'il existe, ou le définir sur une valeur par défaut sinon, je recommande également le

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

comme dans la réponse d'Andrew Wilkinson.

Il existe une troisième solution si vous stockez des objets modifiables dans votre dictionnaire. Un exemple courant pour cela est un multimap , où vous stockez une liste d'éléments pour vos clés. Dans ce cas, vous pouvez utiliser:

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

Si une valeur pour key n'existe pas dans le dictionnaire, la méthode setdefault la définira sur le deuxième paramètre de setdefault. Il se comporte exactement comme un my_dict [clé] standard, renvoyant la valeur de la clé (qui peut être la nouvelle valeur définie).

sd.
la source
ce qui ressemble en effet à Pythonic (à un étranger comme moi) est que toute question a au moins 3 réponses valides :)
davka
@davka: Eh bien, les trois cas d'utilisation sont presque les mêmes, mais différents: a) savoir s'il y a un élément non-None dans le dictionnaire b) récupérer une valeur dans le dictionnaire ou utiliser une valeur par défaut si la valeur n'existe pas c) récupérer une valeur dans le dictionnaire et stocker une valeur par défaut si la valeur n'existe pas encore.
nd.
Je sais :) ce n'est pas une critique, je suis juste amusé par ce fait
davka
Dans un commentaire à la réponse de @ ryeguy, Stuart Woodward suggère que "la surcharge dans la gestion des exceptions dans les langues est toujours un ordre de grandeur supérieur à la recherche dans la table de hachage qui détermine si l'élément existe ou non dans le dictionnaire", tandis que vous dites "Il évite également la recherche de clé en double dans le dictionnaire ... si la recherche est coûteuse "- quelqu'un a-t-il des mesures de l'endroit où la gestion des exceptions est plus rapide ou plus lente qu'une recherche à double clé?
Michael Firth
1
@MichaelFirth J'ai fait une recherche rapide pour les frais généraux d'exception de Python: stackoverflow.com/questions/2522005/… c'est plus lent, mais pas beaucoup. Gardez à l'esprit que le concept de haut niveau de lever une exception est géré très différemment dans différentes langues et vous ne pouvez pas généraliser les avantages et les inconvénients. Ainsi, même si les «exceptions ont une surcharge de 10x» peuvent être correctes pour Java, ce n'est pas pour Python (ou Swift ou autres).
nd.
13

D'accord avec cgoldberg. Comment je fais:

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

Donc, faites-le comme ci-dessus ou utilisez un dict par défaut comme d'autres l'ont suggéré. N'utilisez pas d'instructions if. Ce n'est pas Pythonic.

ryeguy
la source
8
Comment les déclarations if ne sont-elles pas en Pythonic?
Adam Parkin,
2
Je pense que c'est un cas où l'EAFP de Python n'est pas le meilleur moyen. Votre exemple ci-dessus a du code en double; et si un jour nous voulons +=2ou -=1? Vous devez vous rappeler de changer les deux lignes. Cela peut sembler anodin maintenant, mais ce sont le genre de petits bugs stupides qui peuvent revenir vous mordre.
Cam Jackson
3
Cela a l'air bien et fonctionne bien, mais j'évite généralement de le faire comme ça parce que je pensais que la surcharge dans la gestion des exceptions dans les langues est toujours un ordre de grandeur supérieur à la recherche dans la table de hachage qui détermine si l'élément existe ou non dans le dictionnaire.
Stuart Woodward
11

Comme vous pouvez le voir dans les nombreuses réponses, il existe plusieurs solutions. Une instance de LBYL (regardez avant de sauter) n'a pas encore été mentionnée, la méthode 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
bortzmeyer
la source
6
has_key () est plus lent que l'opérateur 'in' et est moins lisible.
Abgan
9
... et il est déconseillé dans Python 2.6 et supprimé dans Python 3.
Tim Pietzcker
7

La façon dont vous essayez de le faire s'appelle LBYL (regardez avant de sauter), car vous vérifiez les conditions avant d'essayer d'augmenter votre valeur.

L'autre approche est appelée EAFP (plus facile de demander pardon puis permission). Dans ce cas, vous essayez simplement l'opération (incrémentez la valeur). S'il échoue, vous interceptez l'exception et définissez la valeur sur 1. Il s'agit d'une façon légèrement plus Pythonique de le faire (IMO).

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

Corey Goldberg
la source
5

Un peu tard mais ça devrait marcher.

my_dict = {}
my_dict[key] = my_dict[key] + 1 if key in my_dict else 1
Bob
la source
Wow, en tant que programmeur Java, c'est une construction assez folle. Il ressemble à un opérateur ternaire étrangement ordonné?
forresthopkinsa
5

Cela ne répond pas directement à la question, mais pour moi, il semble que vous souhaitiez peut-être la fonctionnalité des collections .

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))

Cela se traduit par la sortie

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
Izaak van Dongen
la source
Le compteur fonctionne comme defaultdict(int)avec certaines fonctionnalités supplémentaires, il fonctionnerait donc parfaitement lorsqu'il s'agit exclusivement d'entiers, mais vous ne montrez aucun des comportements pertinents.
Tadhg McDonald-Jensen,
4

Voici une ligne que j'ai récemment trouvée pour résoudre ce problème. Il est basé sur la méthode du dictionnaire setdefault :

my_dict = {}
my_dict[key] = my_dict.setdefault(key, 0) + 1
Igor Gai
la source
0

Je le cherchais, je ne l'ai pas trouvé sur le web puis j'ai tenté ma chance avec Try / Error et je l'ai trouvé

my_dict = {}

if my_dict.__contains__(some_key):
  my_dict[some_key] += 1
else:
  my_dict[some_key] = 1
AbhishekKr
la source
1
Vous ne devriez pas utiliser __contains__dans un code de production. btw. __contains__revient à utiliser is.
user1767754
1
my_dict.__contains__(some_key)est équivalent à some_key in my_dict, n'est pas une surcharge pour l' inopérateur nonis
Tadhg McDonald-Jensen