¿Cómo actualizar un valor, dada una clave en un hashmap?

625

Supongamos que tenemos un HashMap<String, Integer>en Java.

¿Cómo actualizo (incremento) el valor entero de la cadena-clave para cada existencia de la cadena que encuentro?

Uno podría quitar y volver a entrar en el par, pero los gastos generales serían una preocupación.
Otra forma sería simplemente poner el nuevo par y el viejo sería reemplazado.

En el último caso, ¿qué sucede si hay una colisión de código hash con una nueva clave que estoy tratando de insertar? El comportamiento correcto para una tabla hash sería asignarle un lugar diferente o hacer una lista en el depósito actual.

laertis
fuente

Respuestas:

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

debería estar bien. Actualizará el valor de la asignación existente. Tenga en cuenta que esto utiliza el auto-boxeo. Con la ayuda de map.get(key)obtenemos el valor de la clave correspondiente, luego puede actualizar con su requerimiento. Aquí estoy actualizando para incrementar el valor en 1.

Matthew Flaschen
fuente
21
De hecho, es la solución empresarial más robusta y escalable.
Lavir el Whiolet
12
@Lavir, no es una mala solución, pero no veo cómo es la más robusta y escalable. Un atomicinteger en cambio es mucho más escalable.
John Vint
13
esto supone que la clave existe, ¿verdad? Obtengo nullPointer Exception cuando no lo hace.
Ian
84
Con Java 8, esto se puede evitar fácilmente utilizando getOrDefault, por ejemplo:map.put(key, count.getOrDefault(key, 0) + 1);
Martin
2
@Martin .. map.put (key, map.getOrDefault (key, 0) + 1)
Sábado
112

Manera Java 8:

Puede usar el computeIfPresentmétodo y proporcionarle una función de mapeo, que se llamará para calcular un nuevo valor basado en uno existente.

Por ejemplo,

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

Alternativamente, puede usar el mergemétodo, donde 1 es el valor predeterminado y la función incrementa el valor existente en 1:

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

Además, hay un montón de otros métodos útiles, tales como putIfAbsent, getOrDefault, forEach, etc.

Damluar
fuente
3
Acabo de probar sus soluciones. El segundo, el que tiene referencia de método, funciona. La primera, la expresión lambda, no funciona de manera consistente cuando cualquier valor de su mapa es null(digamos words.put("hello", null);), el resultado aún nullno 1es el que esperaría.
Tao Zhang
44
Desde Javadoc: "Si el valor de la clave especificada está presente y no es nulo, intenta calcular una nueva asignación". Puede usar compute()en su lugar, también manejará nullvalores.
Damluar
Quiero incrementar mi valor en 1. .mergees mi solución con Integer::sum.
S_K
48
hashmap.put(key, hashmap.get(key) + 1);

El método putse reemplace el valor de una clave existente y se creará si no existe.

oracleruiz
fuente
55
No, no crea, da nullPointer Exception.
smttsp 01 de
13
El código es una respuesta correcta para la pregunta dada, pero se publicó un año después de que se publicó exactamente el mismo código en la respuesta aceptada. Lo que diferencia esta respuesta es decir que put puede crear una nueva entrada, que puede, pero no en este ejemplo. Si está utilizando hashmap.get (key) para una clave / valor inexistente, obtendrá un valor nulo y cuando intente aumentar, como @smttsp dice que será NPE. -1
Zach
8
Esta respuesta es incorrecta. NullPointerException para claves no existentes
Eleanore
@smttp NullpointterException solo si no inicializó el valor (como sabe, no puede incrementar nulo)
Mehdi
Duplicación y explicación incorrecta ... y la creará si no existe No puede hacerlo null + 1ya que esto intentará desempaquetar nullen un entero para hacer el incremento.
AxelH
43

La forma simplificada de Java 8 :

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

Esto utiliza el método de HashMap que recupera el valor de una clave, pero si la clave no se puede recuperar, devuelve el valor predeterminado especificado (en este caso, un '0').

Esto es compatible con Java central: HashMap <K, V> getOrDefault (clave de objeto, V defaultValue)

Christopher Bull
fuente
3
Este es uno mejor en caso de que estés en Java 1.8
Hemant Nagpal
30

Reemplace Integerpor AtomicIntegery llame a uno de los métodos incrementAndGet/ getAndIncrementen él.

Una alternativa es envolver un inten su propia MutableIntegerclase que tiene un increment()método, solo tiene una preocupación de seguridad de hilos para resolver todavía.

BalusC
fuente
37
AtomicInteger es un entero mutable pero incorporado. Dudo seriamente que escribir tu propio MutableInteger sea una mejor idea.
Peter Lawrey
La costumbre MutableIntegeres mejor, como AtomicIntegerusos volatile, que tiene gastos generales. Yo usaría en int[1]lugar de MutableInteger.
Oliv
@Oliv: no concurrente.
BalusC
@BalusC pero aún así, la escritura volátil es más costosa. Invalida los cachés. Si no hubiera diferencia, todas las variables serían volátiles.
Oliv
@Oliv: la pregunta menciona explícitamente la colisión de código hash, por lo que la concurrencia es importante para OP.
BalusC
19

Solución de una línea:

map.put(key, map.containsKey(key) ? map.get(key) + 1 : 1);
Punktum
fuente
44
Eso no agrega nada nuevo a las respuestas existentes, ¿verdad?
Robert
1
Sí lo hace. Su respuesta marcada correcta arrojará una NullPointerException si la clave no existe. Esta solución funcionará bien.
Hemant Nagpal
18

La solución de @ Matthew es la más simple y funcionará lo suficientemente bien en la mayoría de los casos.

Si necesita un alto rendimiento, AtomicInteger es una mejor solución ala @BalusC.

Sin embargo, una solución más rápida (siempre que la seguridad de los hilos no sea un problema) es usar TObjectIntHashMap, que proporciona un método de incremento (clave) y usa primitivas y menos objetos que crear AtomicIntegers. p.ej

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

Puede aumentar como a continuación, pero debe verificar la existencia para que no se produzca una NullPointerException

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

¿Existe el hash (con 0 como valor) o se "pone" en el mapa en el primer incremento? Si se "pone" en el primer incremento, el código debería verse así:

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

Puede que sea un poco tarde, pero aquí están mis dos centavos.

Si está utilizando Java 8, puede utilizar el método computeIfPresent . Si el valor para la clave especificada está presente y no es nulo, entonces intenta calcular una nueva asignación dada la clave y su valor asignado actual.

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]

También podemos hacer uso de otro método putIfAbsent para poner una clave. Si la clave especificada no está ya asociada a un valor (o está asignada a nulo), este método la asocia al valor dado y devuelve nulo; de lo contrario, devuelve el valor actual.

En caso de que el mapa se comparte a través de las discusiones a continuación, podemos hacer uso de ConcurrentHashMapy AtomicInteger . Del documento:

Un AtomicIntegeres un valor int que puede actualizarse atómicamente. Un AtomicInteger se usa en aplicaciones tales como contadores incrementados atómicamente, y no se puede usar como reemplazo de un Integer. Sin embargo, esta clase sí extiende el Número para permitir el acceso uniforme de herramientas y utilidades que se ocupan de clases basadas en números.

Podemos usarlos como se muestra:

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]

Un punto a tener en cuenta es que estamos invocando getpara obtener el valor de la clave By luego invocando incrementAndGet()su valor, que es, por supuesto AtomicInteger. Podemos optimizarlo ya que el método putIfAbsentdevuelve el valor de la clave si ya está presente:

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

En una nota al margen si planeamos usar AtomicLong, entonces, según la documentación bajo alta contención, el rendimiento esperado de LongAdder es significativamente mayor, a expensas de un mayor consumo de espacio. También revise esta pregunta .

akhil_mittal
fuente
5

La solución más limpia sin NullPointerException es:

map.replace(key, map.get(key) + 1);
Sergey Dirin
fuente
55
si la clave no existe, map.get (key) arrojará NPE
Navi
Sí, eso es verdad
Sergey Dirin
2

Como no puedo comentar algunas respuestas debido a la menor reputación, publicaré una solución que apliqué.

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
fuente
1

Use un forbucle para incrementar el í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
fuente
3
Eso simplemente sobrescribiría el Mapa en cada iteración. Vea la respuesta de Matthew para el enfoque correcto.
Leigh
1
Integer i = map.get(key);
if(i == null)
   i = (aValue)
map.put(key, i + 1);

o

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

Entero son tipos de datos primitivos http://cs.fit.edu/~ryan/java/language/java-data.html , por lo que debe extraerlo, realizar algún proceso y luego volver a colocarlo. Si tiene un valor que no son tipos de datos primitivos, solo necesita extraerlo, procesarlo, no es necesario volver a colocarlo en el hashmap.

Kreedz Zhen
fuente
1
Gracias por este fragmento de código, que puede proporcionar ayuda inmediata. Una explicación adecuada mejoraría en gran medida su valor educativo al mostrar por qué esta es una buena solución al problema y la haría más útil para futuros lectores con preguntas similares, pero no idénticas. Por favor, editar su respuesta para agregar explicación y dar una indicación de lo que se aplican limitaciones y supuestos.
Toby Speight
0

Tratar:

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

Puede cambiar la clave o el valor en su hashmap, pero no puede cambiar ambos al mismo tiempo.

NARAYANAN.M
fuente
0

Utilice Java8 integrado en la función 'computeIfPresent'

Ejemplo:

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
fuente