Jak zaktualizować wartość, biorąc pod uwagę klucz w haszapie?

624

Załóżmy, że mamy HashMap<String, Integer>Javę.

Jak zaktualizować (zwiększyć) wartość całkowitą klucza ciągu dla każdego istnienia znalezionego ciągu?

Można usunąć i ponownie wprowadzić parę, ale narzut byłby problemem.
Innym sposobem byłoby po prostu umieszczenie nowej pary, a stara zostanie zastąpiona.

W tym drugim przypadku, co się stanie, jeśli dojdzie do zderzenia kodu skrótu z nowym kluczem, który próbuję wstawić? Prawidłowe zachowanie tablicy mieszającej oznaczałoby przypisanie jej innego miejsca lub utworzenie z niej listy w bieżącym segmencie.

laertis
źródło

Odpowiedzi:

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

powinno być dobrze. Zaktualizuje wartość istniejącego mapowania. Pamiętaj, że używa to automatycznego boksu. Za pomocą map.get(key)możemy uzyskać wartość odpowiedniego klucza, a następnie możesz zaktualizować zgodnie z wymaganiami. Tutaj aktualizuję wartość przyrostu o 1.

Matthew Flaschen
źródło
21
W rzeczywistości jest to najbardziej niezawodne i skalowalne rozwiązanie dla przedsiębiorstw.
Lavir the Whiolet,
12
@Lavir, nie jest to złe rozwiązanie, ale nie widzę, jak jest najbardziej wytrzymały i skalowalny. Zamiast tego atomowy licznik jest znacznie bardziej skalowalny.
John Vint,
13
to zakłada, że ​​klucz istnieje, prawda? Dostaję wyjątek nullPointer, gdy tak nie jest.
Ian
84
W Javie 8 można tego łatwo uniknąć, używając getOrDefaultna przykład:map.put(key, count.getOrDefault(key, 0) + 1);
Martina
2
@Martin .. map.put (klucz, map.getOrDefault (klucz, 0) + 1)
Sathesh
110

Java 8 sposób:

Możesz użyć computeIfPresentmetody i podać jej funkcję mapowania, która zostanie wywołana w celu obliczenia nowej wartości na podstawie istniejącej.

Na przykład,

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

Alternatywnie można użyć mergemetody, gdzie 1 jest wartością domyślną, a funkcja zwiększa istniejącą wartość o 1:

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

Dodatkowo, istnieje kilka innych przydatnych metod, takich jak putIfAbsent, getOrDefault, forEach, etc.

damluar
źródło
3
Właśnie przetestowałem twoje rozwiązania. Drugi, ten z odniesieniem do metody, działa. Pierwszy, wyrażenie lambda, nie działa konsekwentnie, gdy dowolna wartość twojej mapy jest null(powiedzmy words.put("hello", null);), wynik wciąż nullnie jest taki, 1jak się spodziewałbym.
Tao Zhang
3
Z Javadoc: „Jeśli wartość dla określonego klucza jest obecna i ma wartość inną niż null, próbuje obliczyć nowe mapowanie”. Możesz użyć compute()zamiast tego, będzie również obsługiwać nullwartości.
damluar
Chcę zwiększyć moją wartość o 1. .mergeto moje rozwiązanie Integer::sum.
S_K
48
hashmap.put(key, hashmap.get(key) + 1);

Metoda putbędzie zastąpić wartość istniejącego klucza i stworzy go, jeśli nie istnieje.

oracleruiz
źródło
55
Nie, nie tworzy, daje nullPointer Exception.
smttsp,
13
Kod jest poprawną odpowiedzią na dane pytanie, ale został opublikowany rok po tym, jak dokładnie ten sam kod został zamieszczony w zaakceptowanej odpowiedzi. To, co wyróżnia tę odpowiedź, to stwierdzenie, że put może utworzyć nowy wpis, co może, ale nie w tym przykładzie. Jeśli używasz hashmap.get (klucz) dla nieistniejącego klucza / wartości, otrzymasz null, a gdy spróbujesz zwiększyć, jak @smttsp mówi, że będzie to NPE. -1
Zach
8
Ta odpowiedź jest zła. NullPointerException dla nieistniejących kluczy
Eleanore
@smttp NullpointterException tylko wtedy, gdy nie zainicjowałeś wartości (bo wiesz, że nie możesz zwiększyć wartości null)
Mehdi
Duplikacja i niepoprawne wyjaśnienie ... i utworzy je, jeśli nie istnieje. Nie możesz tego zrobić, null + 1ponieważ spowoduje to rozpakowanie nullliczby całkowitej w celu wykonania przyrostu.
AxelH
43

Uproszczony sposób Java 8 :

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

Korzysta z metody HashMap, która pobiera wartość klucza, ale jeśli klucza nie można odzyskać, zwraca określoną wartość domyślną (w tym przypadku „0”).

Jest to obsługiwane w rdzeniu Java: HashMap <K, V> getOrDefault (Klucz obiektu, V defaultValue)

Christopher Bull
źródło
3
To jest lepsze, jeśli jesteś na java 1.8
Hemant Nagpal
30

Zamień Integerna AtomicIntegeri wywołaj jedną z metod incrementAndGet/ getAndIncrement.

Alternatywą jest zawinięcie intwe własną MutableIntegerklasę, która ma increment()metodę, do rozwiązania jest jeszcze tylko kwestia bezpieczeństwa wątków.

BalusC
źródło
37
AtomicInteger to Zmienna liczba całkowita, ale wbudowana. Poważnie wątpię, aby napisanie własnej MutableInteger było lepszym pomysłem.
Peter Lawrey,
Niestandardowe MutableIntegerjest lepsze, jak AtomicIntegerzastosowania volatile, które mają narzut. Użyłbym int[1]zamiast MutableInteger.
Oliv,
@Oliv: nie współbieżne.
BalusC
@BalusC, ale nadal niestabilny zapis jest droższy. Unieważnia pamięci podręczne. Gdyby nie było różnicy, wszystkie zmienne byłyby zmienne.
Oliv,
@Oliv: pytanie wyraźnie wspomina o kolizji kodu mieszającego, więc współbieżność jest ważna dla OP.
BalusC,
19

Rozwiązanie jednoliniowe:

map.put(key, map.containsKey(key) ? map.get(key) + 1 : 1);
Punktum
źródło
4
To nie dodaje nic nowego do istniejących odpowiedzi, prawda?
Robert,
1
Tak. Poprawna zaznaczona odpowiedź spowoduje wygenerowanie wyjątku NullPointerException, jeśli klucz nie istnieje. To rozwiązanie będzie działać dobrze.
Hemant Nagpal,
18

@ Rozwiązanie Matthew jest najprostsze i w większości przypadków będzie działać wystarczająco dobrze.

Jeśli potrzebujesz wysokiej wydajności, AtomicInteger jest lepszym rozwiązaniem ala @BalusC.

Jednak szybszym rozwiązaniem (pod warunkiem, że bezpieczeństwo wątków nie stanowi problemu) jest użycie TObjectIntHashMap, która zapewnia metodę przyrostu (klucza) i wykorzystuje operacje podstawowe i mniej obiektów niż tworzenie AtomicIntegers. na przykład

TObjectIntHashMap<String> map = new TObjectIntHashMap<String>()
map.increment("aaa");
Peter Lawrey
źródło
13

Możesz zwiększyć jak poniżej, ale musisz sprawdzić istnienie, aby wyjątek NullPointerException nie został zgłoszony

if(!map.containsKey(key)) {
 p.put(key,1);
}
else {
 p.put(key, map.getKey()+1);
}
isuru
źródło
9

Czy istnieje skrót (z wartością 0), czy też jest on „wstawiany” do mapy przy pierwszym kroku? Jeśli zostanie „wstawiony” do pierwszego przyrostu, kod powinien wyglądać następująco:

if (hashmap.containsKey(key)) {
    hashmap.put(key, hashmap.get(key)+1);
} else { 
    hashmap.put(key,1);
}
sudoBen
źródło
7

Może być trochę późno, ale oto moje dwa centy.

Jeśli używasz Java 8, możesz skorzystać z metody computeIfPresent . Jeśli wartość określonego klucza jest obecna i ma wartość inną niż null, wówczas próbuje obliczyć nowe odwzorowanie, biorąc pod uwagę klucz i jego bieżącą wartość odwzorowaną.

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]

Możemy również użyć innej metody putIfAbsent do wprowadzenia klucza. Jeśli określony klucz nie jest już powiązany z wartością (lub jest odwzorowany na wartość NULL), wówczas ta metoda wiąże go z podaną wartością i zwraca wartość NULL, w przeciwnym razie zwraca bieżącą wartość.

W przypadku, gdy mapa jest wspólna dla wątków następnie możemy wykorzystać ConcurrentHashMapi AtomicInteger . Z dokumentu:

An AtomicIntegerjest wartością całkowitą, która może być aktualizowana atomowo. AtomicInteger jest używany w aplikacjach takich jak liczniki z przyrostem atomowym i nie może być stosowany jako zamiennik liczby całkowitej. Jednak ta klasa rozszerza liczbę, aby umożliwić jednolity dostęp do narzędzi i programów narzędziowych, które zajmują się klasami opartymi na liczbach.

Możemy ich używać, jak pokazano:

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]

Należy zauważyć, że wzywamy, getaby uzyskać wartość klucza, Ba następnie wywołujemy incrementAndGet()jej wartość, co oczywiście jest AtomicInteger. Możemy go zoptymalizować, ponieważ metoda putIfAbsentzwraca wartość klucza, jeśli już istnieje:

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

Na marginesie, jeśli planujemy użyć AtomicLong, to zgodnie z dokumentacją w wysokiej rywalizacji spodziewana przepustowość LongAdder jest znacznie wyższa, kosztem wyższego zużycia miejsca. Sprawdź także to pytanie .

akhil_mittal
źródło
5

Czystsze rozwiązanie bez NullPointerException to:

map.replace(key, map.get(key) + 1);
Siergiej Dirin
źródło
5
jeśli klucz nie istnieje, map.get (klucz) wyrzuci NPE
Navi
Tak, to prawda
Sergey Dirin
2

Ponieważ nie mogę skomentować kilku odpowiedzi z powodu mniejszej reputacji, opublikuję rozwiązanie, które zastosowałem.

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
źródło
1

Użyj forpętli, aby zwiększyć indeks:

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
źródło
3
To po prostu nadpisuje mapę przy każdej iteracji. Zobacz odpowiedź Matthew na prawidłowe podejście.
Leigh
1
Integer i = map.get(key);
if(i == null)
   i = (aValue)
map.put(key, i + 1);

lub

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

Liczba całkowita jest pierwotnymi typami danych http://cs.fit.edu/~ryan/java/language/java-data.html , więc musisz ją wyjąć, zrobić trochę procesu, a następnie odłożyć. jeśli masz wartość, która nie jest pierwotnym typem danych, wystarczy ją wyjąć, przetworzyć, nie trzeba ponownie umieszczać jej w haszapie.

Kreedz Zhen
źródło
1
Dziękujemy za ten fragment kodu, który może zapewnić natychmiastową pomoc. Właściwe wyjaśnienie znacznie poprawiłoby jego wartość edukacyjną, pokazując, dlaczego jest to dobre rozwiązanie problemu, i uczyniłoby to bardziej użytecznym dla przyszłych czytelników z podobnymi, ale nie identycznymi pytaniami. Proszę edytować swoje odpowiedzi, aby dodać wyjaśnienie, i dać wskazówkę co zastosować ograniczenia i założenia.
Toby Speight
0

Próbować:

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

UWAGA:

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

Możesz zmienić klucz lub wartość w haszapie, ale nie możesz zmienić obu jednocześnie.

NARAYANAN.M
źródło
0

Użyj wbudowanej funkcji Java8 „computeIfPresent”

Przykład:

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.
źródło