I have two maps:
Map<A, Collection<B>> mapAB
Map<B, Collection<C>> mapBC
I would like to transform them into a Map<A, Collection<C>> mapAC
and I'm wondering if there's a smooth way to do that with lambdas and transformations. In my particular case, the collections are all sets, but I'd like to solve the problem for collections in general.
One thought I had was to first combine the two maps into a Map<A, Map<B, Collection<C>>>
and then flatten it, but I'm open to any approach.
Data notes: B
should only occur in the value collection associated with one A
, and the same is true for mapBC
(a given C
is only mapped to from one B
). As a result, there should only be one path from a given A
to a given C
, although there may be A -> B
mappings for which there are no B -> C
mappings and there may be B -> C
mappings for which there are no corresponding A -> B
mappings. These orphans simply don't appear in the resulting mapAC
.
For the sake of comparison, here's an example of a purely imperative approach to the same problem:
Map<A, Collection<C>> mapAC = new HashMap<>();
for (Entry<A, Collection<B>> entry : mapAB.entrySet()) {
Collection<C> cs = new HashSet<>();
for (B b : entry.getValue()) {
Collection<C> origCs = mapBC.get(b);
if (origCs != null) {
cs.addAll(origCs);
}
}
if (!cs.isEmpty()) {
mapAC.put(entry.getKey(), cs);
}
}
You didn't specify what you would want to do if some b from the first map don't exist in the second map, so this may not be exactly what you are looking for.
mapAB.entrySet().stream()
.filter(e -> e.getValue().stream().anyMatch(mapBC::containsKey))
.collect(toMap(
Map.Entry::getKey,
e->e.getValue().stream()
.filter(mapBC::containsKey)
.map(mapBC::get)
.flatMap(Collection::stream)
.collect(toList())
));
I'm not a fan of the forEach
approach, which is awkwardly imperative. A purer approach might be
mapAB.entrySet().stream()
.flatMap(
entryAB -> entryAB.getValue().stream().flatMap(
b -> mapBC.getOrDefault(b, Collections.<C>emptyList())
.stream().map(
c -> new AbstractMap.SimpleEntry<>(entryAB.getKey(), c))))
// we now have a Stream<Entry<A, C>>
.groupingBy(
Entry::getKey,
mapping(Entry::getValue, toList()));
...or maybe alternately
mapA.entrySet().stream()
.flatMap(
entryAB -> entryAB.getValue().stream().map(
b -> new AbstractMap.SimpleEntry<>(
entryAB.getKey(),
mapBC.getOrDefault(b, Collections.<C>emptyList()))))
// we now have a Stream<Entry<A, Collection<C>>>
.groupingBy(
Entry::getKey,
mapping(Entry::getValue,
reducing(
Collections.<C>emptyList(),
(cs1, cs2) -> {
List<C> merged = new ArrayList<>(cs1);
merged.addAll(cs2);
return merged;
})));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With