Verifique se uma chave já existe em um dicionário e aumente-a

295

Dado um dicionário, como posso descobrir se uma determinada chave nesse dicionário já foi definida como um valor diferente de Nenhum?

Ou seja, eu quero fazer isso:

my_dict = {}

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

Ou seja, eu quero incrementar o valor se já houver um lá ou configurá-lo para 1 caso contrário.

Ben
fonte
11
Pequeno código nitpick: o código define my_dict [key] como 1 se já houver algo lá e o incrementa se não houver. Eu acho que você quer ==, não! =.
QuantumFool 23/06

Respostas:

331

Você está procurando collections.defaultdict(disponível para Python 2.5+). este

from collections import defaultdict

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

fará o que você quiser.

Para Python regulares dict, se não houver valor para uma determinada chave, você não obterá Noneao acessar o dict - a KeyErrorserá gerado. Então, se você quiser usar um dictcódigo regular , em vez do seu código, você usaria

if key in my_dict:
    my_dict[key] += 1
else:
    my_dict[key] = 1
dF.
fonte
8
De acordo com seu exemplo, deve ser suficiente definir "defaultdict (lambda: 0)" e pular toda a cláusula "if".
Deestan
Isso funciona, mas confunde chaves e valores (dificultando a leitura). 'some_value' deve ser 'some_key'
mikemaccana
@ thumbnailer: fixo, obrigado. Inicialmente, usei 'some_value', já que esse é o nome da variável na pergunta, mas concordo que agora é mais claro.
dF.
20
... ou para dicts regulares , você pode fazer my_dict[key] = my_dict.get(key, 0) + 1.
minmaxavg
Como estender isso para dicionários aninhados? ditado [tecla1] [tecla2] + = 1?
Pablo Ruiz Ruiz
301

Eu prefiro fazer isso em uma linha de código.

my_dict = {}

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

Os dicionários têm uma função, get, que aceita dois parâmetros - a chave desejada e um valor padrão, se não existir. Prefiro esse método ao padrão, já que você deseja lidar apenas com o caso em que a chave não existe nessa linha de código, não em todos os lugares.

Andrew Wilkinson
fonte
1
@AndrewWilkinson my bad. Não leu sua resposta tão completamente quanto eu deveria.
masaers
59

Eu pessoalmente gosto de usar setdefault()

my_dict = {}

my_dict.setdefault(some_key, 0)
my_dict[some_key] += 1
kichik
fonte
setdefaulté incrível. Não altera o valor se já estiver definido some_key. Por exemplo, d={1:2}; d.setdefault(1, 0)não perturba o valor de d[1].
wsaleem
49

Você precisa do key in dictidioma para isso.

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

No entanto, você provavelmente deve considerar o uso defaultdict(como sugerido pelo dF).

Eli Bendersky
fonte
1
Observe que em pelo menos 2.6 has_key () foi excluído em favor da chave em d. Eu acho que foi assim também no 2.5.
David Locke
Note-se que se pode escrever my_dict[key] is not None, que é mais clara (IMHO pelo menos)
Brandizzi
@brandizzi - concordar,if key in my_dict and my_dict[key]:
Rob Grant
18

Para responder à pergunta " como posso descobrir se um determinado índice nesse ditado já foi definido como um valor diferente de Nenhum ", eu preferiria isso:

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

Isso está de acordo com o conceito já invocado de EAFP (mais fácil pedir perdão do que permissão). Também evita a pesquisa de chave duplicada no dicionário, como faria emkey in my_dict and my_dict[key] is not None interessante se a pesquisa fosse cara.

Para o problema real que você colocou, por exemplo, incrementar um int, se existir, ou defini-lo com um valor padrão, caso contrário, também recomendo

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

como na resposta de Andrew Wilkinson.

Existe uma terceira solução se você estiver armazenando objetos modificáveis ​​no seu dicionário. Um exemplo comum disso é um multimap , onde você armazena uma lista de elementos para suas chaves. Nesse caso, você pode usar:

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

Se um valor para chave não existir no dicionário, o método setdefault o definirá como o segundo parâmetro de setdefault. Ele se comporta exatamente como um padrão my_dict [key], retornando o valor da chave (que pode ser o novo valor definido).

nd.
fonte
o que parece na verdade Pythonic (para um estranho como eu) é que qualquer questão tem pelo menos 3 respostas válidas :)
davka
@avka: Bem, os três casos de uso são quase os mesmos, mas diferentes: a) descubra se existe um elemento diferente de None no dicionário b) recupere um valor do dicionário ou use um padrão se o valor não existir c) recupere um valor do dicionário e armazene um padrão se o valor ainda não existir.
nd.
Eu sei :) isto não é uma crítica, eu estou apenas divertindo-se com este fato
davka
Em um comentário à resposta do @ ryeguy, Stuart Woodward sugere "a sobrecarga no tratamento de exceções nos idiomas é sempre uma ordem de magnitude maior que a pesquisa da tabela de hash que determina se o item existe ou não no dicionário", enquanto você está dizendo "Isso também evita a pesquisa de chave duplicada no dicionário ... se a pesquisa for cara "- alguém tem alguma medida de onde o tratamento de exceções é mais rápido ou mais lento do que uma pesquisa de chave dupla?
Michael Firth
1
@ MichaelFirth Fiz uma pesquisa superficial da sobrecarga de exceção do Python: stackoverflow.com/questions/2522005/… é mais lenta, mas não muito. Lembre-se de que o conceito de alto nível de lançar uma exceção é tratado de maneira muito diferente em idiomas diferentes e você não pode generalizar os prós e os contras. Portanto, embora as "Exceções tenham uma sobrecarga de 10x" possam estar corretas para Java, não é para Python (ou Swift ou outros).
nd.
13

Concordou com cgoldberg. Como eu faço isso é:

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

Portanto, faça-o como acima, ou use um ditado padrão, como outros sugeriram. Não use instruções if. Isso não é pitonico.

ryeguy
fonte
8
Como as declarações if não são pitonicas?
quer
2
Eu acho que esse é um caso em que o EAFP do Python não é o melhor caminho. Seu exemplo acima tem código duplicado; e se um dia quisermos +=2ou -=1? Você precisa se lembrar de mudar as duas linhas. Pode parecer uma coisa trivial agora, mas esses são os tipos de insetos "triviais" estúpidos que podem voltar a morder você.
Cam Jackson
3
Isso parece bom e funciona bem, mas geralmente evito fazê-lo assim, porque pensei que a sobrecarga no tratamento de exceções nos idiomas é sempre uma ordem de magnitude maior que a pesquisa da tabela de hash que determina se o item existe ou não no dicionário.
Stuart Woodward
11

Como você pode ver nas muitas respostas, existem várias soluções. Uma instância do LBYL (veja antes de saltar) ainda não foi mencionada, o 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
fonte
6
has_key () é mais lento que o operador 'in' e é menos legível.
Abgan
9
... e tem sido preterida no Python 2.6 e removido em Python 3.
Tim Pietzcker
7

A maneira como você está tentando fazer isso é chamada LBYL (veja antes de pular), pois você está verificando as condições antes de tentar aumentar seu valor.

A outra abordagem é chamada EAFP (mais fácil pedir perdão do que permissão). Nesse caso, você apenas tentaria a operação (aumentaria o valor). Se falhar, você capturar a exceção e definir o valor como 1. Essa é uma maneira ligeiramente mais Pythonic de fazer isso (IMO).

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

Corey Goldberg
fonte
5

Um pouco tarde, mas isso deve funcionar.

my_dict = {}
my_dict[key] = my_dict[key] + 1 if key in my_dict else 1
Prumo
fonte
Uau, como programador Java, esse é um construto bem louco. Parece um operador ternário de ordem estranha?
forresthopkinsa
5

Isso não está respondendo diretamente à pergunta, mas, para mim, parece que você pode querer a funcionalidade das coleções .

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

Isso resulta na saída

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
fonte
O contador funciona da mesma maneira que defaultdict(int)com algumas funcionalidades extras, por isso funcionaria perfeitamente ao lidar exclusivamente com números inteiros, mas você não mostra nenhum comportamento relevante.
Tadhg McDonald-Jensen
4

Aqui está uma frase que eu inventei recentemente para resolver esse problema. É baseado no método de dicionário setdefault :

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

Eu estava procurando, não encontrei na web, tentei a sorte com Try / Error e encontrei

my_dict = {}

if my_dict.__contains__(some_key):
  my_dict[some_key] += 1
else:
  my_dict[some_key] = 1
AbhishekKr
fonte
1
Você não deveria estar usando __contains__em um código de produção. btw. __contains__é o mesmo que usar is.
user1767754
1
my_dict.__contains__(some_key)é equivalente a some_key in my_dict, é sobrecarga para o inoperador nãois
Tadhg McDonald-Jensen