Como atualizar um valor, dada uma chave em um hashmap?

624

Suponha que tenhamos um HashMap<String, Integer>em Java.

Como atualizo (incremento) o valor inteiro da chave de cadeia para cada existência da cadeia que encontro?

Pode-se remover e reinserir o par, mas a sobrecarga seria uma preocupação.
Outra maneira seria colocar o novo par e o antigo seria substituído.

Neste último caso, o que acontece se houver uma colisão de código de hash com uma nova chave que estou tentando inserir? O comportamento correto para uma hashtable seria atribuir um local diferente para ele ou fazer uma lista dele no intervalo atual.

Laertis
fonte

Respostas:

972
map.put(key, map.get(key) + 1);

deve ficar bem. Ele atualizará o valor para o mapeamento existente. Observe que isso usa boxe automático. Com a ajuda de map.get(key)obtermos o valor da chave correspondente, você poderá atualizar com sua exigência. Aqui estou atualizando para aumentar o valor em 1.

Matthew Flaschen
fonte
21
De fato, essa é a solução corporativa mais robusta e escalável.
Lavir the Whiolet
12
@Lavir, não é uma solução ruim, mas não vê como é a mais robusta e escalável. Um atomicinteger é muito mais escalável.
precisa
13
isso assume que a chave existe, certo? Estou recebendo a exceção nullPointer quando isso não acontece.
31414 Ian
84
Com o Java 8, isso pode ser facilmente evitado usando getOrDefault, por exemplo:map.put(key, count.getOrDefault(key, 0) + 1);
Martin
2
@Martin .. map.put (chave, map.getOrDefault (chave, 0) + 1)
Sathesh
112

Maneira Java 8:

Você pode usar o computeIfPresentmétodo e fornecer a ele uma função de mapeamento, que será chamada para calcular um novo valor com base no valor existente.

Por exemplo,

Map<String, Integer> words = new HashMap<>();
words.put("hello", 3);
words.put("world", 4);
words.computeIfPresent("hello", (k, v) -> v + 1);
System.out.println(words.get("hello"));

Como alternativa, você pode usar o mergemétodo, em que 1 é o valor padrão e a função incrementa o valor existente em 1:

words.merge("hello", 1, Integer::sum);

Além disso, há um conjunto de outros métodos úteis, tais como putIfAbsent, getOrDefault, forEach, etc.

damluar
fonte
3
Acabei de testar suas soluções. O segundo, aquele com referência ao método, funciona. O primeiro, a expressão lambda, não está funcionando de maneira consistente quando qualquer valor do seu mapa é null(digamos words.put("hello", null);), o resultado ainda nullnão é o 1que eu esperaria.
Tao Zhang
4
Do Javadoc: "Se o valor da chave especificada estiver presente e não for nulo, tentará calcular um novo mapeamento". Você pode usar compute(), em vez disso, ele manipulará nullvalores também.
damluar
Eu quero incrementar meu valor em 1. .mergeÉ a minha solução com Integer::sum.
S_K 02/04
48
hashmap.put(key, hashmap.get(key) + 1);

O método putirá substituir o valor de uma chave existente e criará-lo se não existe.

oracleruiz
fonte
55
Não, não cria, dá nullPointer Exception.
21815 smttsp #
13
O código é uma resposta correta para a pergunta em questão, mas foi postado um ano depois que o mesmo código exato foi postado na resposta aceita. A única coisa que diferencia esta resposta é a de colocar, pode criar uma nova entrada, o que pode, mas não neste exemplo. Se você estiver usando hashmap.get (chave) para uma chave / valor inexistente, você será nulo e quando tentar incrementar, como @smttsp diz que será NPE. -1
Zach
8
Esta resposta está errada. NullPointerException para chaves inexistentes
Eleanore
@smttp NullpointterException somente se você não inicializar o valor (como você sabe que você não pode incremento null)
Mehdi
Duplicação e explicação incorreta ... e a criará se não existir. Você não pode fazer, null + 1pois isso tentará descompactar o arquivo nullem um número inteiro para fazer o incremento.
AxelH 16/03/19
43

A maneira simplificada do Java 8 :

map.put(key, map.getOrDefault(key, 0) + 1);

Isso usa o método do HashMap que recupera o valor de uma chave, mas se a chave não puder ser recuperada, ela retornará o valor padrão especificado (neste caso, um '0').

Isso é suportado no Java principal: HashMap <K, V> getOrDefault (Chave do objeto, V defaultValue)

Christopher Bull
fonte
3
Este é um é melhor no caso de você estar em java 1.8
Hemant Nagpal
30

Substitua Integerpor AtomicIntegere chame um dos métodos incrementAndGet/ getAndIncrementnele.

Uma alternativa é agrupar uma classe intem sua própria MutableIntegerclasse que possua um increment()método, você só tem uma preocupação de segurança de thread a resolver ainda.

BalusC
fonte
37
AtomicInteger é um número inteiro mutável, mas embutido. Eu duvido seriamente que escrever seu próprio MutableInteger seja uma idéia melhor.
Peter Peterrey
Personalizado MutableIntegeré melhor, como AtomicIntegerusos volatile, que tem sobrecarga. Eu usaria em int[1]vez de MutableInteger.
Oliv #
@ Oliv: não concorrente.
BalusC
@ BalusC, mas ainda assim, a gravação volátil é mais cara. Invalida caches. Se não houvesse diferença, todas as variáveis ​​seriam voláteis.
Oliv #
@ Oliv: pergunta menciona explicitamente a colisão de código de hash, portanto a simultaneidade é importante para o OP.
BalusC
19

Solução de uma linha:

map.put(key, map.containsKey(key) ? map.get(key) + 1 : 1);
Punktum
fonte
4
Isso não acrescenta nada de novo às respostas existentes, acrescenta?
Robert
1
Sim. A resposta marcada correta lançará uma NullPointerException se a chave não existir. Esta solução irá funcionar bem.
precisa
18

@ A solução de Matthew é a mais simples e terá bom desempenho na maioria dos casos.

Se você precisar de alto desempenho, o AtomicInteger é uma solução melhor, ala @BalusC.

No entanto, uma solução mais rápida (desde que a segurança do encadeamento não seja um problema) é usar TObjectIntHashMap, que fornece um método de incremento (chave) e usa primitivos e menos objetos do que a criação de AtomicIntegers. por exemplo

TObjectIntHashMap<String> map = new TObjectIntHashMap<String>()
map.increment("aaa");
Peter Lawrey
fonte
13

Você pode incrementar como abaixo, mas precisa verificar a existência para que uma NullPointerException não seja lançada

if(!map.containsKey(key)) {
 p.put(key,1);
}
else {
 p.put(key, map.getKey()+1);
}
isuru
fonte
9

O hash existe (com 0 como valor) ou é "colocado" no mapa no primeiro incremento? Se for "colocado" no primeiro incremento, o código deve se parecer com:

if (hashmap.containsKey(key)) {
    hashmap.put(key, hashmap.get(key)+1);
} else { 
    hashmap.put(key,1);
}
sudoBen
fonte
7

Pode ser um pouco tarde, mas aqui estão meus dois centavos.

Se você estiver usando o Java 8, poderá usar o método computeIfPresent . Se o valor da chave especificada estiver presente e não for nulo, ele tentará calcular um novo mapeamento, dada a chave e seu valor mapeado atual.

final Map<String,Integer> map1 = new HashMap<>();
map1.put("A",0);
map1.put("B",0);
map1.computeIfPresent("B",(k,v)->v+1);  //[A=0, B=1]

Também podemos fazer uso de outro método putIfAbsent para colocar uma chave. Se a chave especificada ainda não estiver associada a um valor (ou estiver mapeada para nula), esse método a associará ao valor fornecido e retornará nulo; caso contrário, retornará o valor atual.

No caso do mapa é compartilhado entre threads, então podemos fazer uso de ConcurrentHashMape AtomicInteger . Do documento:

An AtomicIntegeré um valor int que pode ser atualizado atomicamente. Um AtomicInteger é usado em aplicativos como contadores incrementados atomicamente e não pode ser usado como um substituto para um número inteiro. No entanto, esta classe estende Number para permitir acesso uniforme por ferramentas e utilitários que lidam com classes numericamente baseadas.

Podemos usá-los como mostrado:

final Map<String,AtomicInteger> map2 = new ConcurrentHashMap<>();
map2.putIfAbsent("A",new AtomicInteger(0));
map2.putIfAbsent("B",new AtomicInteger(0)); //[A=0, B=0]
map2.get("B").incrementAndGet();    //[A=0, B=1]

Um ponto a observar é que estamos invocando getpara obter o valor da chave Be, em seguida, invocando incrementAndGet()seu valor, que é claro AtomicInteger. Podemos otimizá-lo, pois o método putIfAbsentretorna o valor da chave, se já estiver presente:

map2.putIfAbsent("B",new AtomicInteger(0)).incrementAndGet();//[A=0, B=2]

Além disso, se planejarmos usar o AtomicLong , conforme a documentação sob alta contenção, a taxa de transferência esperada do LongAdder será significativamente maior, à custa de um maior consumo de espaço. Verifique também esta pergunta .

akhil_mittal
fonte
5

A solução mais limpa sem NullPointerException é:

map.replace(key, map.get(key) + 1);
Sergey Dirin
fonte
5
se a chave não existe, então map.get (chave) vai jogar NPE
Navi
Sim, isso é verdade
Sergey Dirin
2

Como não posso comentar algumas respostas devido à menor reputação, publicarei uma solução que apliquei.

for(String key : someArray)
{
   if(hashMap.containsKey(key)//will check if a particular key exist or not 
   {
      hashMap.put(hashMap.get(key),value+1);// increment the value by 1 to an already existing key
   }
   else
   {
      hashMap.put(key,value);// make a new entry into the hashmap
   }
}
aayush nigam
fonte
1

Use um forloop para incrementar o índice:

for (int i =0; i<5; i++){
    HashMap<String, Integer> map = new HashMap<String, Integer>();
    map.put("beer", 100);

    int beer = map.get("beer")+i;
    System.out.println("beer " + beer);
    System.out ....

}
VanHoutte
fonte
3
Isso apenas sobrescreveria o mapa em cada iteração. Veja a resposta de Mateus para a abordagem correta.
Leigh
1
Integer i = map.get(key);
if(i == null)
   i = (aValue)
map.put(key, i + 1);

ou

Integer i = map.get(key);
map.put(key, i == null ? newValue : i + 1);

Inteiro é tipos de dados primitivos http://cs.fit.edu/~ryan/java/language/java-data.html , então você precisa removê-lo, fazer algum processo e colocá-lo novamente. se você tiver um valor que não seja do tipo Primitivo, precisará retirá-lo, processá-lo e não precisará colocá-lo novamente no mapa de hash.

Kreedz Zhen
fonte
1
Obrigado por este trecho de código, que pode fornecer ajuda imediata. Uma explicação adequada melhoraria muito seu valor educacional, mostrando por que essa é uma boa solução para o problema e a tornaria mais útil para futuros leitores com perguntas semelhantes, mas não idênticas. Por favor edite sua resposta para adicionar explicação, e dar uma indicação do que limitações e premissas se aplicam.
precisa
0

Tentar:

HashMap hm=new HashMap<String ,Double >();

NOTA:

String->give the new value; //THIS IS THE KEY
else
Double->pass new value; //THIS IS THE VALUE

Você pode alterar a chave ou o valor no seu hashmap, mas não pode alterar as duas ao mesmo tempo.

NARAYANAN.M
fonte
0

Use Java8 construído em função 'computeIfPresent'

Exemplo:

public class ExampleToUpdateMapValue {

    public static void main(String[] args) {
        Map<String,String> bookAuthors = new TreeMap<>();
        bookAuthors.put("Genesis","Moses");
        bookAuthors.put("Joshua","Joshua");
        bookAuthors.put("Judges","Samuel");

        System.out.println("---------------------Before----------------------");
        bookAuthors.entrySet().stream().forEach(System.out::println);
        // To update the existing value using Java 8
        bookAuthors.computeIfPresent("Judges", (k,v) -> v = "Samuel/Nathan/Gad");

        System.out.println("---------------------After----------------------");
        bookAuthors.entrySet().stream().forEach(System.out::println);
    }
}
Rajesh D
fonte