ハッシュマップのキーを指定して値を更新する方法は?


624

HashMap<String, Integer>Java があるとします。

見つかった文字列が存在するたびに、文字列キーの整数値を更新(増分)するにはどうすればよいですか?

ペアを削除して再入力することもできますが、オーバーヘッドが問題になります。
別の方法は、新しいペアを置くだけで、古いペアを置き換えることです。

後者の場合、挿入しようとしている新しいキーとのハッシュコードの衝突があるとどうなりますか?ハッシュテーブルの正しい動作は、ハッシュテーブルに別の場所を割り当てるか、現在のバケットでハッシュテーブルからリストを作成することです。

回答:


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

大丈夫です。既存のマッピングの値を更新します。これはオートボクシングを使用していることに注意してください。助けを借りてmap.get(key)、我々は、キーに対応する値を取得し、その後、あなたはあなたの要件を更新することができます。ここでは、値を1ずつ増やすように更新しています。


21
実際、これは最も堅牢でスケーラブルなエンタープライズソリューションです。
Lavir the Whiolet 2010

12
@Lavir、それは悪いソリューションではありませんが、その最も堅牢でスケーラブルな方法を見てはいけません。代わりに、atomicintegerははるかにスケーラブルです。
John Vint

13
これはキーが存在することを前提としていますよね?そうでないときにnullPointer Exceptionが発生します。
2014

84
Java 8では、これを使用することで簡単に回避できます。getOrDefault例:map.put(key, count.getOrDefault(key, 0) + 1);
Martin

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

112

Java 8の方法:

computeIfPresentmethodを使用してマッピング関数を提供できます。これは、既存の値に基づいて新しい値を計算するために呼び出されます。

例えば、

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

または、mergeメソッドを使用することもできます。ここで、1はデフォルト値であり、関数は既存の値を1ずつ増分します。

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

加えて、このような他の有用な方法の束がありputIfAbsentgetOrDefaultforEach、など


3
私はあなたのソリューションをテストしました。2番目のメソッドは、メソッド参照を持つもので機能します。最初の1つであるラムダ式1は、マップの値がnull(たとえばwords.put("hello", null);)である場合に一貫して機能nullせず1、期待どおりの結果が得られません。
Tao Zhang、

4
Javadocから:「指定されたキーの値が存在し、nullでない場合、新しいマッピングを計算しようとします」。compute()代わりに使用でき、null値も処理します。
damluar

値を1増やしたいのです.mergeが、での解決策Integer::sumです。
S_K

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

このメソッドputは既存のキーの値を置き換え、存在しない場合は作成します。


55
いいえ、作成されませんnullPointer Exception
smttsp 2015

13
コードは特定の質問に対する正しい回答ですが、受け入れられた回答にまったく同じコードが投稿されてから1年後に投稿されました。この回答を差別化するのは、putは新しいエントリを作成できるということですが、この例ではできません。存在しないキー/値にhashmap.get(key)を使用している場合は、@ smttspがNPEとするようにnullを取得し、インクリメントしようとすると、-1
Zach

8
この答えは間違っています。存在しないキーのNullPointerException
Eleanore

@smttp NullpointterExceptionは、値を初期化しなかった場合にのみ(nullをインクリメントできないことがわかっているため)
Mehdi

重複と不正確な説明... 存在しない場合は作成しますnull + 1これはnull整数にボックス化解除して増分を実行しようとするため、実行できません。
AxelH 2017年

43

単純化されたJava 8の方法:

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

これは、キーの値を取得するHashMapのメソッドを使用しますが、キーを取得できない場合は、指定されたデフォルト値(この場合は「0」)を返します。

これはコアJava内でサポートされています:HashMap <K、V> getOrDefault(Object key、V defaultValue)


3
これは、Java 1.8を使用している場合に適しています
Hemant Nagpal 2017

30

置き換えIntegerAtomicInteger、その上でincrementAndGet/ getAndIncrementメソッドの1つを呼び出します。

別の方法は、メソッドを持つint独自のMutableIntegerクラスでをラップするincrement()ことです。これで解決できるのはスレッドセーフの懸念だけです。


37
AtomicIntegerは、可変整数ですが組み込みです。独自のMutableIntegerを書く方が良い考えだと私は真剣に疑います。
Peter Lawrey、2011

カスタムMutableIntegerとして、優れているAtomicInteger用途volatileのオーバーヘッドがあります。のint[1]代わりに使用しますMutableInteger
Oliv 2017年

@Oliv:同時ではありません。
BalusC 2017年

@BalusCですが、揮発性書き込みの方が高価です。キャッシュを無効にします。違いがなければ、すべての変数が揮発性になります。
Oliv

@Oliv:質問はハッシュコードの衝突を明示的に言及しているため、OPにとって並行性は重要です。
BalusC 2017年

19

1行のソリューション:

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

4
それは既存の答えに新しいものを追加しませんか?
Robert

1
はい、そうです。キーが存在しない場合、正しいマークされた回答はNullPointerExceptionをスローします。このソリューションは正常に動作します。
Hemant Nagpal 2017

18

@マシューのソリューションは最もシンプルで、ほとんどの場合十分に機能します。

高いパフォーマンスが必要な場合、AtomicIntegerは@BalusCとしてより優れたソリューションです。

ただし、より高速なソリューション(スレッドの安全性が問題ではない場合)は、TObjectIntHashMapを使用することです。TObjectIntHashMapは、increment(key)メソッドを提供し、プリミティブを使用し、AtomicIntegerを作成するよりも少ないオブジェクトを使用します。例えば

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

13

以下のようにインクリメントできますが、NullPointerExceptionがスローされないように存在を確認する必要があります

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

9

ハッシュは存在しますか(値として0を使用)、または最初の増分でマップに「プット」されますか?最初のインクリメントに「プット」される場合、コードは次のようになります。

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

7

少し遅いかもしれませんが、ここに私の2セントがあります。

Java 8を使用している場合は、computeIfPresentメソッドを使用できます。指定されたキーの値が存在し、nullでない場合、キーとその現在のマッピングされた値を指定して、新しいマッピングを計算しようとします。

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]

別のメソッドputIfAbsentを使用してキーを置くこともできます。指定されたキーがまだ値に関連付けられていない(またはnullにマップされている)場合、このメソッドはそれを指定された値に関連付けてnullを返し、そうでない場合は現在の値を返します。

マップがスレッド間で共有されている場合はConcurrentHashMapAtomicIntegerを使用できます。ドキュメントから:

An AtomicIntegerは、アトミックに更新できるint値です。AtomicIntegerは、アトミックに増分されるカウンターなどのアプリケーションで使用され、Integerの代わりとして使用することはできません。ただし、このクラスはNumberを拡張して、数値ベースのクラスを処理するツールやユーティリティによる均一なアクセスを可能にします。

以下のように使用できます。

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]

get注意すべき点の1つは、keyの値を取得するためにB呼び出してincrementAndGet()から、その値(当然のことAtomicIntegerです)を呼び出すことです。メソッドputIfAbsentがすでに存在する場合、キーの値を返すため、最適化できます。

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

余談ですがAtomicLongの使用を計画している場合は、ドキュメンテーションのとおり、競合が多い状況で、LongAdderの予想スループットは大幅に高くなりますが、スペースの消費量が多くなります。この質問もチェックしてください。


5

NullPointerExceptionのないよりクリーンなソリューションは次のとおりです。

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

5
キーが存在しない場合、map.get(key)はNPEをスローします
ナビ

はい、そうです
セルゲイディ

2

評判が低いため、いくつかの回答にはコメントできないため、適用したソリューションを投稿します。

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

forループを使用してインデックスを増分します。

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
これは、各反復でマップを上書きするだけです。正しいアプローチについては、マシューの答えを参照してください。
リー


1
Integer i = map.get(key);
if(i == null)
   i = (aValue)
map.put(key, i + 1);

または

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

整数はプリミティブデータタイプhttp://cs.fit.edu/~ryan/java/language/java-data.htmlであるため、取り出してプロセスを実行し、元に戻す必要があります。プリミティブデータ型ではない値がある場合は、それを取り出して処理するだけで、ハッシュマップに戻す必要はありません。


1
このコードスニペット、ありがとうございます。適切な説明は、なぜこれが問題の優れた解決策であるを示すことによって教育的価値を大幅に改善し、同様の、しかし同一ではない質問を持つ将来の読者にとってより有用になります。回答を編集して説明を追加し、適用される制限と前提を示してください。
Toby Speight 2017

0

試してください:

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

注意:

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

ハッシュマップのキーまたは値のいずれかを変更できますが、両方を同時に変更することはできません。


0

機能「computeIfPresent」に組み込まれたJava8を使用する

例:

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);
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.