Matt Passell Matt Passell - 6 months ago 16
Java Question

Using Java 8 lambdas/transformations to combine and flatten two Maps

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);
}
}

Answer

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())
  ));
Comments