Comment mettre à jour une valeur, étant donné une clé dans une table de hachage?

625

Supposons que nous ayons un HashMap<String, Integer>en Java.

Comment mettre à jour (incrémenter) la valeur entière de la clé de chaîne pour chaque existence de la chaîne que je trouve?

On pourrait retirer et rentrer la paire, mais les frais généraux seraient une préoccupation.
Une autre façon serait de simplement mettre la nouvelle paire et l'ancienne serait remplacée.

Dans ce dernier cas, que se passe-t-il en cas de collision de hashcode avec une nouvelle clé que j'essaie d'insérer? Le comportement correct d'une table de hachage consiste à lui attribuer un emplacement différent ou à en créer une liste dans le compartiment actuel.

laertis
la source

Réponses:

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

devrait être bien. Il mettra à jour la valeur du mappage existant. Notez que cela utilise la boxe automatique. Avec l'aide de map.get(key)nous obtenons la valeur de la clé correspondante, vous pouvez alors mettre à jour votre besoin. Ici, je mets à jour pour incrémenter la valeur de 1.

Matthew Flaschen
la source
21
Il s'agit en fait de la solution d'entreprise la plus robuste et la plus évolutive.
Lavir le Whiolet
12
@Lavir, ce n'est pas une mauvaise solution, mais ne voyez pas comment c'est le plus robuste et évolutif. Un atomicinteger est à la place beaucoup plus évolutif.
John Vint
13
cela suppose que la clé existe, non? Je reçois une exception nullPointer quand ce n'est pas le cas.
Ian
84
Avec Java 8, cela peut facilement être évité en utilisant getOrDefault, par exemple:map.put(key, count.getOrDefault(key, 0) + 1);
Martin
2
@Martin .. map.put (clé, map.getOrDefault (clé, 0) + 1)
Sathesh
112

Java 8 voies:

Vous pouvez utiliser la computeIfPresentméthode et lui fournir une fonction de mappage, qui sera appelée pour calculer une nouvelle valeur en fonction de celle existante.

Par exemple,

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

Alternativement, vous pouvez utiliser la mergeméthode, où 1 est la valeur par défaut et la fonction incrémente la valeur existante de 1:

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

De plus, il y a un tas d'autres méthodes utiles, telles que putIfAbsent, getOrDefault, forEach, etc.

damluar
la source
3
Je viens de tester vos solutions. Le second, celui avec la référence de méthode, fonctionne. La première, l'expression lambda, ne fonctionne pas de manière cohérente lorsqu'une valeur de votre carte est null(disons words.put("hello", null);), le résultat n'est toujours nullpas 1comme je m'y attendais.
Tao Zhang
4
De Javadoc: "Si la valeur de la clé spécifiée est présente et non nulle, tente de calculer un nouveau mappage". Vous pouvez utiliser à la compute()place, il gérera également les nullvaleurs.
damluar
Je veux augmenter ma valeur de 1. .mergeest ma solution avec Integer::sum.
S_K
48
hashmap.put(key, hashmap.get(key) + 1);

La méthode putva remplacer la valeur d'une clé existante et créer si n'existe pas.

oracleruiz
la source
55
Non, ça ne crée pas, ça donne nullPointer Exception.
smttsp
13
Le code est une réponse correcte pour la question donnée, mais a été publié un an après que le même code exact a été publié dans la réponse acceptée. La chose qui différencie cette réponse est que put peut créer une nouvelle entrée, ce qu'elle peut, mais pas dans cet exemple. Si vous utilisez hashmap.get (clé) pour une clé / valeur inexistante, vous obtiendrez null et lorsque vous tenterez d'incrémenter, comme @smttsp le dit, ce sera NPE. -1
Zach
8
Cette réponse est fausse. NullPointerException pour les clés non existantes
Eleanore
@smttp NullpointterException uniquement si vous n'avez pas initialisé la valeur (comme vous le savez, vous ne pouvez pas incrémenter null)
Mehdi
Duplication et explication incorrecte ... et la créera si elle n'existe pas Vous ne pouvez pas faire null + 1car cela va essayer de déballer le nulldans un entier pour faire l'incrément.
AxelH
43

La manière simplifiée de Java 8 :

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

Cela utilise la méthode de HashMap qui récupère la valeur d'une clé, mais si la clé ne peut pas être récupérée, elle renvoie la valeur par défaut spécifiée (dans ce cas, un «0»).

Ceci est pris en charge dans le noyau Java: HashMap <K, V> getOrDefault (clé d'objet, V defaultValue)

Christopher Bull
la source
3
C'est mieux si vous êtes sur Java 1.8
Hemant Nagpal
30

Remplacez Integerpar AtomicIntegeret appelez l'une des méthodes incrementAndGet/ getAndIncrementdessus.

Une alternative consiste à envelopper un intdans votre propre MutableIntegerclasse qui a une increment()méthode, vous n'avez encore qu'un problème de threadsafety à résoudre.

BalusC
la source
37
AtomicInteger est un entier modifiable mais intégré. Je doute sérieusement que l'écriture de votre propre MutableInteger soit une meilleure idée.
Peter Lawrey
La coutume MutableIntegerest meilleure, comme les AtomicIntegerutilisations volatile, ce qui a des frais généraux. J'utiliserais int[1]plutôt MutableInteger.
Oliv
@Oliv: non simultané.
BalusC
@BalusC mais quand même, l'écriture volatile coûte plus cher. Il invalide les caches. S'il n'y avait aucune différence, toutes les variables seraient volatiles.
Oliv
@Oliv: la question mentionne explicitement la collision de hashcode, donc la concurrence est importante pour OP.
BalusC
19

Solution en une ligne:

map.put(key, map.containsKey(key) ? map.get(key) + 1 : 1);
Punktum
la source
4
Cela n'ajoute rien de nouveau aux réponses existantes, n'est-ce pas?
Robert
1
Oui. La réponse marquée correcte lèvera une exception NullPointerException si la clé n'existe pas. Cette solution fonctionnera bien.
Hemant Nagpal
18

@ La solution de Matthew est la plus simple et fonctionnera assez bien dans la plupart des cas.

Si vous avez besoin de hautes performances, AtomicInteger est une meilleure solution ala @BalusC.

Cependant, une solution plus rapide (à condition que la sécurité des threads ne soit pas un problème) consiste à utiliser TObjectIntHashMap qui fournit une méthode d'incrémentation (clé) et utilise des primitives et moins d'objets que la création d'AtomicIntegers. par exemple

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

Vous pouvez incrémenter comme ci-dessous mais vous devez vérifier son existence afin qu'une NullPointerException ne soit pas levée

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

Le hachage existe-t-il (avec 0 comme valeur) ou est-il "mis" sur la carte au premier incrément? S'il est "mis" sur le premier incrément, le code devrait ressembler à:

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

Il est peut-être un peu tard mais voici mes deux cents.

Si vous utilisez Java 8, vous pouvez utiliser la méthode computeIfPresent . Si la valeur de la clé spécifiée est présente et non nulle, elle tente de calculer un nouveau mappage en fonction de la clé et de sa valeur mappée actuelle.

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]

Nous pouvons également utiliser une autre méthode putIfAbsent pour mettre une clé. Si la clé spécifiée n'est pas déjà associée à une valeur (ou est mappée à null), cette méthode l'associe à la valeur donnée et renvoie null, sinon renvoie la valeur actuelle.

Dans le cas où la carte est partagée entre plusieurs threads, nous pouvons utiliser ConcurrentHashMapet AtomicInteger . Du doc:

An AtomicIntegerest une valeur int qui peut être mise à jour atomiquement. Un AtomicInteger est utilisé dans des applications telles que les compteurs à incrémentation atomique et ne peut pas être utilisé en remplacement d'un Integer. Cependant, cette classe étend Number pour permettre un accès uniforme par des outils et des utilitaires qui traitent des classes à base numérique.

Nous pouvons les utiliser comme indiqué:

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 point à observer est que nous invoquons getpour obtenir la valeur de la clé B, puis invoquons incrementAndGet()sa valeur qui est bien sûr AtomicInteger. Nous pouvons l'optimiser car la méthode putIfAbsentrenvoie la valeur de la clé si elle est déjà présente:

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

D'un autre côté, si nous prévoyons d'utiliser AtomicLong, alors, selon la documentation, le débit attendu de LongAdder est considérablement plus élevé, au détriment d'une consommation d'espace plus élevée. Vérifiez également cette question .

akhil_mittal
la source
5

La solution la plus propre sans NullPointerException est:

map.replace(key, map.get(key) + 1);
Sergey Dirin
la source
5
si la clé n'existe pas alors map.get (clé) lancera NPE
Navi
Oui c'est vrai
Sergey Dirin
2

Comme je ne peux pas commenter quelques réponses en raison d'une réputation moindre, je publierai une solution que j'ai appliquée.

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
la source
1

Utilisez une forboucle pour incrémenter l'index:

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
la source
3
Cela remplacerait simplement la carte à chaque itération. Voir la réponse de Matthew pour la bonne approche.
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);

Entier correspond aux types de données primitifs http://cs.fit.edu/~ryan/java/language/java-data.html , vous devez donc les supprimer, effectuer un processus, puis les remettre. si vous avez une valeur qui n'est pas un type de données primitif, il vous suffit de la retirer, de la traiter, pas besoin de la remettre dans la table de hachage.

Kreedz Zhen
la source
1
Merci pour cet extrait de code, qui peut fournir une aide immédiate. Une explication appropriée améliorerait considérablement sa valeur éducative en montrant pourquoi il s'agit d'une bonne solution au problème, et la rendrait plus utile aux futurs lecteurs ayant des questions similaires, mais pas identiques. Veuillez modifier votre réponse pour ajouter des explications et donner une indication des limitations et hypothèses applicables.
Toby Speight
0

Essayer:

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

REMARQUE:

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

Vous pouvez modifier la clé ou la valeur dans votre table de hachage, mais vous ne pouvez pas modifier les deux en même temps.

NARAYANAN.M
la source
0

Utilisez Java8 intégré dans fuction 'computeIfPresent'

Exemple:

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
la source