Reputation: 3323
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?
Upvotes: 18
Views: 24060
Reputation: 3323
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
Upvotes: 1
Reputation: 17289
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;});
});
Upvotes: 0
Reputation: 4089
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.
Upvotes: 0
Reputation: 159086
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()))));
Upvotes: 4
Reputation: 6991
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:
Stream<Map<String,List<String>>>
Entry<String, List<String>>
Pair<String, String>
Upvotes: 3
Reputation: 131326
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;}
));
Upvotes: 4
Reputation: 56393
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.
Upvotes: 24
Reputation: 2923
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.
Upvotes: 4