Come aggiornare un valore, data una chiave in una hashmap?


625

Supponiamo di avere un HashMap<String, Integer>in Java.

Come posso aggiornare (incrementare) il valore intero della chiave di stringa per ogni esistenza della stringa che trovo?

Si potrebbe rimuovere e rientrare nella coppia, ma l'overhead sarebbe una preoccupazione.
Un altro modo sarebbe quello di mettere la nuova coppia e quella vecchia sarebbe stata sostituita.

In quest'ultimo caso, cosa succede se si verifica una collisione hashcode con una nuova chiave che sto cercando di inserire? Il comportamento corretto per un hashtable sarebbe quello di assegnare un posto diverso per esso, o fare un elenco da esso nel bucket corrente.

Risposte:


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

dovrebbe andare bene. Aggiornerà il valore per la mappatura esistente. Si noti che questo utilizza il boxing automatico. Con l'aiuto di map.get(key)otteniamo il valore della chiave corrispondente, quindi puoi aggiornare con il tuo requisito. Qui sto aggiornando per incrementare il valore di 1.


21
In effetti, questa è la soluzione aziendale più solida e scalabile.
Lavir il Whiolet l'

12
@Lavir, non è una cattiva soluzione, ma non vedo come sia il più robusto e scalabile. Un atomicinteger invece è molto più scalabile.
John Vint,

13
questo presuppone che la chiave esista, giusto? Ricevo un'eccezione nullPointer quando non lo fa.
Ian

84
Con Java 8, questo può essere facilmente evitato usando getOrDefault, ad esempio:map.put(key, count.getOrDefault(key, 0) + 1);
Martin

2
@Martin .. map.put (key, map.getOrDefault (key, 0) + 1)
Sathesh

112

Java 8 vie:

È possibile utilizzare il computeIfPresentmetodo e fornire una funzione di mappatura, che verrà chiamata per calcolare un nuovo valore basato su uno esistente.

Per esempio,

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

In alternativa, è possibile utilizzare il mergemetodo, dove 1 è il valore predefinito e la funzione incrementa il valore esistente di 1:

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

Inoltre, v'è un gruppo di altri metodi utili, quali putIfAbsent, getOrDefault, forEach, etc.


3
Ho appena testato le tue soluzioni. Il secondo, quello con riferimento al metodo, funziona. Il primo, l'espressione lambda uno, non sta funzionando in modo coerente quando un qualsiasi valore della vostra mappa è null(diciamo words.put("hello", null);), il risultato è ancora nullnon 1come mi sarei aspettato.
Tao Zhang,

4
Da Javadoc: "Se il valore per la chiave specificata è presente e non nullo, tenta di calcolare una nuova mappatura". Puoi usare compute()invece, gestirà anche i nullvalori.
damluar,

Voglio aumentare il mio valore di 1. .mergeè la mia soluzione con Integer::sum.
S_K

48
hashmap.put(key, hashmap.get(key) + 1);

Il metodo putsi sostituirà il valore di una chiave esistente e crea se non esiste.


55
No, non crea, dà nullPointer Exception.
smttsp,

13
Il codice è una risposta corretta per la domanda data, ma è stato pubblicato un anno dopo che lo stesso codice esatto era stato inserito nella risposta accettata. La cosa che differenzia questa risposta è affermare che può creare una nuova voce, che può, ma non in questo esempio. Se stai usando hashmap.get (chiave) per una chiave / valore inesistente otterrai null e quando tenti di incrementare, come dice @smttsp, sarà NPE. -1
Zach

8
Questa risposta è sbagliata NullPointerException per chiavi inesistenti
Eleanore,

@smttp NullpointterException solo se non hai inizializzato il valore (come sai non puoi incrementare null)
Mehdi

Duplicazione e spiegazione errata ... e la creeranno se non esiste Non puoi farlo null + 1poiché questo proverà a decomprimere la casella nullin un numero intero per fare l'incremento.
AxelH


30

Sostituisci Integercon AtomicIntegere chiama uno dei metodi incrementAndGet/ getAndIncrementsu di esso.

Un'alternativa è quella di avvolgere un intnella tua MutableIntegerclasse che ha un increment()metodo, hai solo un problema di sicurezza del filo da risolvere ancora.


37
AtomicInteger è un intero mutabile ma incorporato. Dubito seriamente che scrivere il tuo MutableInteger sia un'idea migliore.
Peter Lawrey,

L'usanza MutableIntegerè migliore, come AtomicIntegerusi volatile, che ha un sovraccarico. Userei int[1]invece di MutableInteger.
Oliv

@Oliv: non simultaneo.
BalusC

@BalusC ma la scrittura volatile è ancora più costosa. Invalida le cache. Se non ci fosse differenza, tutte le variabili sarebbero volatili.
Oliv

@Oliv: la domanda menziona esplicitamente la collisione di hashcode, quindi la concorrenza è importante per OP.
BalusC

19

Una soluzione di linea:

map.put(key, map.containsKey(key) ? map.get(key) + 1 : 1);

4
Ciò non aggiunge nulla di nuovo alle risposte esistenti, vero?
Robert,

1
Sì, lo fa. La risposta contrassegnata corretta genererà una NullPointerException se la chiave non esiste. Questa soluzione funzionerà bene.
Hemant Nagpal,

18

La soluzione di @ Matthew è la più semplice e funzionerà abbastanza bene nella maggior parte dei casi.

Se hai bisogno di prestazioni elevate, AtomicInteger è una soluzione migliore ala @BalusC.

Tuttavia, una soluzione più rapida (a condizione che la sicurezza del thread non sia un problema) consiste nell'utilizzare TObjectIntHashMap che fornisce un metodo di incremento (chiave) e utilizza primitive e meno oggetti rispetto alla creazione di AtomicIntegers. per esempio

TObjectIntHashMap<String> map = new TObjectIntHashMap<String>()
map.increment("aaa");

13

È possibile incrementare come di seguito ma è necessario verificare l'esistenza in modo che non venga generata una NullPointerException

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

9

L'hash esiste (con 0 come valore) o è "messo" sulla mappa sul primo incremento? Se è "messo" sul primo incremento, il codice dovrebbe apparire come:

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

7

Potrebbe essere un po 'tardi, ma ecco i miei due centesimi.

Se si utilizza Java 8, è possibile utilizzare il metodo computeIfPresent . Se il valore per la chiave specificata è presente e non nullo, tenta di calcolare una nuova mappatura data la chiave e il suo valore corrente mappato.

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]

Possiamo anche usare un altro metodo putIfAbsent per mettere una chiave. Se la chiave specificata non è già associata a un valore (o è mappata su null), questo metodo la associa al valore dato e restituisce null, altrimenti restituisce il valore corrente.

Nel caso in cui la mappa è condivisa tra le discussioni allora possiamo fare uso di ConcurrentHashMape AtomicInteger . Dal documento:

An AtomicIntegerè un valore int che può essere aggiornato atomicamente. Un AtomicInteger viene utilizzato in applicazioni come contatori a incremento atomico e non può essere utilizzato come sostituto di un numero intero. Tuttavia, questa classe estende il numero per consentire l'accesso uniforme da parte di strumenti e utilità che si occupano di classi basate su valori numerici.

Possiamo usarli come mostrato:

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 da osservare è che stiamo invocando getper ottenere il valore per chiave Be quindi invocando incrementAndGet()il suo valore che è ovviamente AtomicInteger. Possiamo ottimizzarlo poiché il metodo putIfAbsentrestituisce il valore per la chiave se già presente:

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

In una nota a margine se intendiamo utilizzare AtomicLong, come da documentazione in contesa elevata, il throughput previsto di LongAdder è significativamente più alto, a scapito di un maggiore consumo di spazio. Controlla anche questa domanda .


5

La soluzione più pulita senza NullPointerException è:

map.replace(key, map.get(key) + 1);

5
se la chiave non esiste, map.get (chiave) genererà NPE
Navi il

Sì, è vero
Sergey Dirin,

2

Dal momento che non posso commentare alcune risposte a causa della minore reputazione, posterò una soluzione che ho applicato.

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

1

Utilizzare un forciclo per incrementare l'indice:

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

}

3
Ciò sovrascriverebbe la mappa su ogni iterazione. Vedi la risposta di Matthew per l'approccio corretto.
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);

Integer è un tipo di dati primitivo http://cs.fit.edu/~ryan/java/language/java-data.html , quindi è necessario estrarlo, eseguire un processo e quindi ripristinarlo. se hai un valore che non è un tipo di dati Primitivo, devi solo estrarlo, elaborarlo, non è necessario reinserirlo nell'hashmap.


1
Grazie per questo frammento di codice, che può fornire un aiuto immediato. Una spiegazione adeguata migliorerebbe notevolmente il suo valore educativo mostrando perché questa è una buona soluzione al problema e la renderebbe più utile ai futuri lettori con domande simili, ma non identiche. Si prega di modificare la risposta di aggiungere una spiegazione, e dare un'indicazione di ciò si applicano le limitazioni e le assunzioni.
Toby Speight,

0

Provare:

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

Puoi modificare la chiave o il valore nella tua hashmap, ma non puoi cambiarli entrambi contemporaneamente.


0

Usa la funzione integrata Java8 'computeIfPresent'

Esempio:

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);
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.