web-dev-qa-db-ja.com

重複するキーを持つ配列のマップをマージします

配列のマップが2つあります。

Map<String, List<String>> map1 = new HashMap<>();
Map<String, List<String>> map2 = new HashMap<>();

それらを1つの新しいマップにマージします。
両方のマップにキーが存在する場合、その場合、配列をマージする必要があります。

例えば:

map1.put("k1", Arrays.asList("a0", "a1"));
map1.put("k2", Arrays.asList("b0", "b1"));

map2.put("k2", Arrays.asList("z1", "z2"));

// Expected output is 
Map 3: {k1=[a0, a1], k2=[b0, b1, z1, z2]}

ストリームでそれをやろうとしました

Map<String, List<String>> map3 = Stream.of(map1, map2)
    .flatMap(map -> map.entrySet().stream())
    .collect(Collectors.toMap(
        Map.Entry::getKey,
        e -> e.getValue().stream().collect(Collectors.toList())
    ));

これは、マップに同じキーがない場合に機能します。そうでなければ、例外が発生します

Exception in thread "main" Java.lang.IllegalStateException: Duplicate key k2 (attempted merging values [b0, b1] and [z1, z2])
    at Java.base/Java.util.stream.Collectors.duplicateKeyException(Collectors.Java:133)
    at Java.base/Java.util.stream.Collectors.lambda$uniqKeysMapAccumulator$1(Collectors.Java:180)
    at Java.base/Java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.Java:169)
    at Java.base/Java.util.HashMap$EntrySpliterator.forEachRemaining(HashMap.Java:1751)
    at Java.base/Java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.Java:658)
    at Java.base/Java.util.stream.ReferencePipeline$7$1.accept(ReferencePipeline.Java:274)
    at Java.base/Java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.Java:948)
    at Java.base/Java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.Java:484)
    at Java.base/Java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.Java:474)
    at Java.base/Java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.Java:913)
    at Java.base/Java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.Java:234)
    at Java.base/Java.util.stream.ReferencePipeline.collect(ReferencePipeline.Java:578)
    at im.djm.Test.main(Test.Java:25)

ストリームでこのタスクを実行する方法はありますか?
または私はthrougマップを繰り返す必要がありますか?

12
djm.im

キーが重複している場合は、マージ関数を使用します。

_Map<String, List<String>> map3 = Stream.of(map1, map2)
                .flatMap(map -> map.entrySet().stream())
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        e -> new ArrayList<>(e.getValue()),
                        (left, right) -> {left.addAll(right); return left;}
                ));
_

マージ関数で追加できる可変リストが常にあることを保証するために、e -> e.getValue().stream().collect(Collectors.toList())new ArrayList<>(e.getValue())に変更したことに注意してください。

11
Ousmane D.

多分。しかし、反復を使用してエントリを手動で結合することで、すべてを正しく行う可能性が高くなります。他の誰かがこのコードに取り組む必要があるかどうかはわかりませんが、読みやすいアプローチに感謝するでしょう。

5
Steve11235

次のようにすることもできます:

Map<String, List<String>> map3 = Stream.concat(map1.entrySet().stream(),
                                               map2.entrySet().stream())
      .collect(Collectors.groupingBy(Entry::getKey,
                   Collectors.mapping(Entry::getValue,
                       Collectors.flatMapping(List::stream,
                           Collectors.toList()))));
4
Andreas

重複したキーをマージできるようにするオーバーロードされたtoMap()バージョンを使用する必要があります。

toMap(Function<? super T, ? extends K> keyMapper,
                                    Function<? super T, ? extends U> valueMapper,
                                    BinaryOperator<U> mergeFunction) 

あなたは次のように書くことができます:

Map<String, List<String>> map3 = Stream.of(map1, map2)
    .flatMap(map -> map.entrySet().stream())
    .collect(Collectors.toMap(
        Map.Entry::getKey,
        e -> new ArrayList<>(e.getValue()),
        (e1, e2) -> { e1.addAll(e2); return e1;}
    ));
2
davidxxx

フラットマップを2回使用する

Map<String, List<String>> map1 = new HashMap<>();
Map<String, List<String>> map2 = new HashMap<>();

map1.put("k1", Arrays.asList("a0", "a1"));
map1.put("k2", Arrays.asList("b0", "b1"));

map2.put("k2", Arrays.asList("z1", "z2"));

Map<String, List<String>> map3 = Stream.of(map1, map2)
        .flatMap(p -> p.entrySet().stream())
        .flatMap(p -> p.getValue().stream().map(q -> new Pair<>(p.getKey(), q)))
        .collect(
                Collectors.groupingBy(
                        p -> p.getKey(),
                        Collectors.mapping(p -> p.getValue(), Collectors.toList())
                )
        );

これは次のように機能します。

  • 両方のマップを取りますStream<Map<String,List<String>>>
  • FlatMapsエントリをEntry<String, List<String>>として
  • エントリーをPair<String, String>ごとに1ペアにFlatMap
  • キーでそれらを収集します
    • 値を取り、それらをリストに収集する
2
jrtapsell

他の方法はこのようになります。

初期化する必要がありますmap3より大きなマップを使用します(ここでmap1)。次に、他のマップ上でループを使用し、mergeメソッドを使用して重複キーを結合します。

Map<String, List<String>> map3 = new HashMap<>(map1);
    for (Map.Entry<String, List<String>> entry : map2.entrySet()) {
       List<String> values = new ArrayList<>(entry.getValue());
       map3.merge(entry.getKey(),entry.getValue(),(l1, l2) -> {values.addAll(l1); 
           return values;
       });
    }

map2.forEach((key, value) -> {
    List<String> values = new ArrayList<>(value);
      map3.merge(key,value, (l1, l2) -> {values.addAll(l1);return values;});
});
0
Hadi J

これは、マップとリストをマージするもう1つの方法です。

_Map<String, List<String>> map3 = Stream.of(map1, map2)
    .flatMap(map -> map.entrySet().stream())
    .collect(Collectors.toMap(
        Map.Entry::getKey,
        Map.Entry::getValue,
        (e1, e2) -> Stream.concat(e1.stream(), e2.stream()).collect(Collectors.toList())
    ));
_

toMapメソッドの3番目の引数は
_(e1, e2) -> Stream.concat(e1.stream(), e2.stream()).collect(Collectors.toList())_はmergeFunction関数です。
この機能は重複に適用されます。

マップされたキーに重複が含まれている場合(Object.equals(Object))に従って)、値マッピング関数が各等しい要素に適用され、提供されたマージ関数を使用して結果がマージされます。
JavaDoc

0
djm.im

以下は、両方のマップの反復を使用した例です。最初の反復では、map1とmap2の共通のキーと値のペアを結合し、それらを結果のマップに追加するか、map1の一意のキーと値のペアを結果のマップに追加します。 2回目の反復では、map1と一致しないmap2の残りすべてを取得し、結果のマップに追加します。

public static Map<String, ArrayList<String>> joinMaps(Map<String, ArrayList<String>> map1, Map<String, ArrayList<String>> map2)
{
    Map<String, ArrayList<String>> mapJoined = new HashMap<>();

    //join values from map2 into values of map1 or add unique key/values of map1
    for (Map.Entry<String, ArrayList<String>> entry : map1.entrySet()) {
        String key = entry.getKey();
        ArrayList<String> value = entry.getValue();
        if(map2.containsKey(key))
        {
            value.addAll(map2.get(key));
            mapJoined.put(key, value);
        }
        else
            mapJoined.put(key, value);
    }

    //add the non-duplicates left over in map 2
    for (Map.Entry<String, ArrayList<String>> entry : map2.entrySet()) {
        if(!mapJoined.containsKey(entry.getKey()))
            mapJoined.put(entry.getKey(), entry.getValue());
    }

    return mapJoined;
}

最初の反復で追加されたすべてのキーを追跡するために関数にSetを追加することもできます。そのSetのサイズ== map2のサイズであれば、マップに同じキーがあり、2番目を繰り返す必要はありません。マップ、マップ2。

0
RAZ_Muh_Taz