This is a bit late but in case the length of Character
and Integer
lists is different, it may be possible to build an inner map containing all Character
keys and null
for missing Integer
values:
// class MyClass
static Map<String, Map<Character, Integer>> joinMaps(
Map<String, List<Character>> map1, Map<String, List<Integer>> map2)
{
return map1
.entrySet()
.stream()
.filter(e -> map2.containsKey(e.getKey())) // keep the keys from both maps
.map(e -> Map.entry(
e.getKey(), // String key for result
IntStream.range(0, e.getValue().size()) // build map <Character, Integer>
.mapToObj(i -> new AbstractMap.SimpleEntry<>(
e.getValue().get(i),
i < map2.get(e.getKey()).size() ? map2.get(e.getKey()).get(i) : null
))
// use custom collector to allow null Integer values
.collect(
MyClass::innerMap,
(hm, e2) -> hm.put(e2.getKey(), e2.getValue()),
Map::putAll
)
))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
static LinkedHashMap<Character, Integer> innerMap() {
return new LinkedHashMap<>();
}
Custom collector for inner map is needed to allow adding null
values which is not possible with Collectors.toMap
where NPE is thrown.
Tests:
Map<String, List<Character>> map1 = Map.of(
"S0", Arrays.asList('#', '@'),
"S1", Arrays.asList('a', 'b'),
"S2", Arrays.asList('c', 'd'),
"S3", Arrays.asList('e', 'f')
);
System.out.println("map1=" + map1);
Map<String, List<Integer>> map2 = Map.of(
"S1", Arrays.asList(1),
"S2", Arrays.asList(3, 4, 5),
"S3", Arrays.asList(5, 6),
"S4", Arrays.asList(7, 8)
);
System.out.println("map2=" + map2);
Map<String, Map<Character, Integer>> res = joinMaps(map1, map2);
System.out.println("----\nResult:");
res.forEach((k, v) -> System.out.printf("%s -> %s%n", k, v));
Output:
map1={S0=[#, @], S1=[a, b], S2=[c, d], S3=[e, f]}
map2={S1=[1], S2=[3, 4, 5], S3=[5, 6], S4=[7, 8]}
----
Result:
S1 -> {a=1, b=null}
S2 -> {c=3, d=4}
S3 -> {e=5, f=6}
Update
Another solution using Stream::flatMap
and groupingBy + mapping
collectors with a custom collector used as a downstream collector is shown below:
static Map<String, Map<Character, Integer>> joinMaps(
Map<String, List<Character>> map1, Map<String, List<Integer>> map2)
{
return map1
.entrySet()
.stream()
.filter(e -> map2.containsKey(e.getKey()))
.flatMap(e -> IntStream.range(0, e.getValue().size())
.mapToObj(i -> Map.entry(
e.getKey(),
new AbstractMap.SimpleEntry<>(
e.getValue().get(i),
i < map2.get(e.getKey()).size() ? map2.get(e.getKey()).get(i) : null
)
))) // Stream<Map.Entry<String, Map.Entry<Character, Integer>>>
.collect(Collectors.groupingBy(
Map.Entry::getKey, // use String as key in outer map
Collectors.mapping(e -> e.getValue(), // build inner map
Collector.of( // using custom collector
MyClass::innerMap, // supplier
(hm, e2) -> hm.put(e2.getKey(), e2.getValue()), // accumulator
MyClass::mergeMaps // combiner
))
));
}
static <T extends Map> T mergeMaps(T acc, T map) {
acc.putAll(map);
return acc;
}
Here mergeMaps
is a BinaryOperator<A> combiner
argument in the method Collector.of
which slightly differs from Stream::collect
used above where BiConsumer<R, R>
is used as combiner.