问题
I have two maps of arrays.
Map<String, List<String>> map1 = new HashMap<>();
Map<String, List<String>> map2 = new HashMap<>();
I want to merge them in one new map.
If a key exists in both maps, in that case, I should merge arrays.
For example:
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]}
I tried to do that with streams
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())
));
This work if there are no the same keys in maps. Otherwise, I get the exception
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)
Is there a way to accomplish this task with streams?
Or I have to iterate throug maps?
回答1:
Use a merge function in the case of duplicate keys:
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;}
));
Note, I've changed e -> e.getValue().stream().collect(Collectors.toList()) to new ArrayList<>(e.getValue()) to guarantee that we always have a mutable list which we can add into in the merge function.
回答2:
Maybe. But you are more likely to get everything right by combining the entries manually, using iteration. I don't know if anyone else will have to work on this code, but they will likely be grateful for an easy to read approach.
回答3:
You can also do it like this:
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:
You have to use the overloaded toMap() version that allows to merge duplicate keys :
toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction)
You could write something as :
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;}
));
回答5:
Using flatmap twice
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())
)
);
This works like this:
- Takes both maps
Stream<Map<String,List<String>>> - FlatMaps the entries as
Entry<String, List<String>> - FlatMaps the entries into 1 pair per
Pair<String, String> - Collects them by their key
- Taking the values, and collecting them into a list
回答6:
Here is an example using iteration of both maps. First iteration joins common key/value pairs from map1 and map2 together and adds them to the resulting map or adds unique key/value pairs in map1 to the resulting map. Second iteration grabs anything leftover in map2 that didnt match map1 and adds them to the resulting map.
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;
}
You could also add a Set into the function to keep track of all keys added in the first iteration, then if the size of that Set == size of the map2 you know the maps have the same keys and there is no need to iterate second map, map2.
回答7:
other way would be like this.
you should init map3 with larger map.(here map1). then use loop over other map and use merge method to combine duplicate key.
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;});
});
回答8:
Here is one another way to merge maps and lists.
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())
));
The third argument in toMap method is(e1, e2) -> Stream.concat(e1.stream(), e2.stream()).collect(Collectors.toList()) is mergeFunction function.
This function is applied to duplicates.
If the mapped keys contains duplicates (according to
Object.equals(Object)), the value mapping function is applied to each equal element, and the results are merged using the provided merging function.
JavaDoc
来源:https://stackoverflow.com/questions/50379529/merge-map-of-arrays-with-duplicate-keys