Compruebe si ya existe una clave determinada en un diccionario e increméntela

295

Dado un diccionario, ¿cómo puedo saber si una clave dada en ese diccionario ya se ha establecido en un valor distinto de Ninguno?

Es decir, quiero hacer esto:

my_dict = {}

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

Es decir, quiero incrementar el valor si ya hay uno allí, o establecerlo en 1 de lo contrario.

Ben
fuente
11
Código pequeño: el código establece my_dict [key] en 1 si ya hay algo allí, y lo incrementa si no lo hay. Creo que quieres ==, no! =.
QuantumFool

Respuestas:

331

Estás buscando collections.defaultdict(disponible para Python 2.5+). Esta

from collections import defaultdict

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

Hará lo que quieras.

Para Python dicts normales , si no hay un valor para una clave determinada, no obtendrá Noneal acceder al dict: se KeyErrorgenerará a. Entonces, si desea usar un dictcódigo regular , en lugar de su código, usaría

if key in my_dict:
    my_dict[key] += 1
else:
    my_dict[key] = 1
dF.
fuente
8
Según su ejemplo, debería ser suficiente establecer "defaultdict (lambda: 0)" y omitir toda la cláusula "if".
Deestan el
Esto funciona, pero confunde claves y valores (lo que hace que sea algo extraño de leer). 'some_value' debería ser 'some_key'
mikemaccana
@nailer: arreglado, gracias. Inicialmente había usado 'some_value' ya que ese es el nombre de la variable en la pregunta, pero estoy de acuerdo en que ahora es más claro.
dF.
20
... o para mensajes regulares dict, puedes hacerlo my_dict[key] = my_dict.get(key, 0) + 1.
minmaxavg
¿Cómo extender esto a los diccionarios anidados? dict [clave1] [clave2] + = 1?
Pablo Ruiz Ruiz
301

Prefiero hacer esto en una línea de código.

my_dict = {}

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

Los diccionarios tienen una función, get, que toma dos parámetros: la clave que desea y un valor predeterminado si no existe. Prefiero este método por defecto ya que solo desea manejar el caso donde la clave no existe en esta línea de código, no en todas partes.

Andrew Wilkinson
fuente
1
@ AndrewWilkinson mi mal. No leí tu respuesta tan a fondo como debería haberlo hecho.
masaers
59

Personalmente me gusta usar setdefault()

my_dict = {}

my_dict.setdefault(some_key, 0)
my_dict[some_key] += 1
kichik
fuente
setdefaultes increíble. No altera el valor si ya hay uno configurado some_key. Por ejemplo, d={1:2}; d.setdefault(1, 0)no perturba el valor de d[1].
wsaleem
49

Necesitas el key in dictidioma para eso.

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

Sin embargo, probablemente debería considerar usar defaultdict(como se sugiere dF).

Eli Bendersky
fuente
1
Tenga en cuenta que al menos 2.6 has_key () ha sido depricateado a favor de la clave en d. Creo que también fue así en 2.5.
David Locke el
Tenga en cuenta que uno puede escribir my_dict[key] is not None, que es más claro (en mi humilde opinión al menos)
brandizzi
@brandizzi - de acuerdo,if key in my_dict and my_dict[key]:
Rob Grant
18

Para responder a la pregunta " ¿cómo puedo saber si un índice dado en ese dict ya se ha establecido en un valor distinto de Ninguno ", preferiría esto:

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

Esto se ajusta al concepto ya invocado de EAFP (es más fácil pedir perdón que permiso). También evita la búsqueda de claves duplicadas en el diccionario como lo haría en key in my_dict and my_dict[key] is not Nonelo que es interesante si la búsqueda es costosa.

Para el problema real que ha planteado, es decir, incrementar un int si existe o establecerlo en un valor predeterminado, de lo contrario, también recomiendo

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

como en la respuesta de Andrew Wilkinson.

Hay una tercera solución si está almacenando objetos modificables en su diccionario. Un ejemplo común de esto es un mapa múltiple , donde almacena una lista de elementos para sus claves. En ese caso, puede usar:

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

Si no existe un valor para clave en el diccionario, el método setdefault lo establecerá en el segundo parámetro de setdefault. Se comporta como un my_dict [clave] estándar, devolviendo el valor de la clave (que puede ser el nuevo valor establecido).

Dakota del Norte.
fuente
lo que parece realmente Pythonic (para un extraño como yo) es que cualquier pregunta tiene al menos 3 respuestas válidas :)
davka
@davka: Bueno, los tres casos de uso son casi iguales, pero diferentes: a) averiguar si hay un elemento que no sea Ninguno en el diccionario b) recuperar un valor del diccionario o usar un valor predeterminado si el valor no existe c) recuperar un valor del diccionario y almacenar un valor predeterminado si el valor aún no existe.
nd.
Lo sé :) esto no es una crítica, simplemente me divierte este hecho
davka
En un comentario a la respuesta de @ ryeguy, Stuart Woodward sugiere que "la sobrecarga en el manejo de excepciones en los idiomas siempre es un orden de magnitud mayor que la búsqueda de la tabla hash que determina si el elemento existe o no en el diccionario", mientras usted dice "Es también evita la búsqueda de claves duplicadas en el diccionario ... si la búsqueda es costosa ", ¿alguien tiene alguna medida de dónde el manejo de excepciones es más rápido o más lento que una búsqueda de doble clave?
Michael Firth
1
@MichaelFirth Hice una búsqueda rápida de los gastos generales de excepción de Python: stackoverflow.com/questions/2522005/ ... es más lento, pero no mucho. Tenga en cuenta que el concepto de alto nivel de lanzar una excepción se maneja de manera muy diferente en diferentes idiomas y no puede generalizar los pros y los contras. Entonces, aunque las "Excepciones tienen una sobrecarga de 10x" pueden ser correctas para Java, no lo es para Python (o Swift u otros).
nd.
13

De acuerdo con cgoldberg. Cómo lo hago es:

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

Por lo tanto, hágalo como se indicó anteriormente o use un dict predeterminado como otros han sugerido. No use declaraciones if. Eso no es pitónico.

ryeguy
fuente
8
¿Cómo son las declaraciones si no son pitónicas?
Adam Parkin
2
Creo que este es un caso en el que EAFP de Python no es la mejor manera. Su ejemplo anterior tiene código duplicado; ¿Qué pasa si un día queremos +=2o -=1? Tienes que recordar cambiar ambas líneas. Puede parecer una cosa trivial ahora, pero esos son el tipo de pequeños bichos 'triviales' que pueden volver a morderte.
Cam Jackson
3
Esto se ve bien y funciona bien, pero generalmente evito hacerlo así porque pensé que la sobrecarga en el manejo de excepciones en idiomas siempre es un orden de magnitud mayor que la búsqueda de tabla hash que determina si el elemento existe o no en el diccionario.
Stuart Woodward
11

Como puede ver en las muchas respuestas, hay varias soluciones. Todavía no se ha mencionado una instancia de LBYL (mira antes de saltar), el método 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
fuente
66
has_key () es más lento que el operador 'in' y es menos legible.
Abgan el
99
... y ha quedado en desuso en Python 2.6 y eliminado en Python 3.
Tim Pietzcker
7

La forma en que intenta hacerlo se llama LBYL (mire antes de saltar), ya que está verificando las condiciones antes de intentar aumentar su valor.

El otro enfoque se llama EAFP (es más fácil pedir perdón que permiso). En ese caso, simplemente intentaría la operación (incremente el valor). Si falla, detecta la excepción y establece el valor en 1. Esta es una forma un poco más pitónica de hacerlo (IMO).

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

Corey Goldberg
fuente
5

Un poco tarde pero esto debería funcionar.

my_dict = {}
my_dict[key] = my_dict[key] + 1 if key in my_dict else 1
Beto
fuente
Wow, como programador de Java, esta es una construcción bastante loca. Parece un operador ternario extrañamente ordenado?
forresthopkinsa
5

Esto no responde directamente a la pregunta, pero para mí, parece que le gustaría la funcionalidad de las colecciones .

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

Esto da como resultado la salida

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
fuente
Counter funciona igual que defaultdict(int)con alguna funcionalidad adicional, por lo que funcionaría perfectamente cuando se trata exclusivamente con enteros, pero no muestra ninguno de los comportamientos relevantes.
Tadhg McDonald-Jensen el
4

Aquí hay una frase que se me ocurrió recientemente para resolver este problema. Se basa en el método de diccionario predeterminado :

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

Lo estaba buscando, no lo encontré en la web, luego probé suerte con Try / Error y lo encontré

my_dict = {}

if my_dict.__contains__(some_key):
  my_dict[some_key] += 1
else:
  my_dict[some_key] = 1
AbhishekKr
fuente
1
No deberías estar usando __contains__un código de producción. por cierto. __contains__es lo mismo que usar is.
user1767754
1
my_dict.__contains__(some_key)es equivalente a some_key in my_dict, es una sobrecarga para el inoperador nois
Tadhg McDonald-Jensen