How do you invert Map<v1, Set<v2>> to Map<v2, Set<v1>> in Java with stream()

Viewed 866

I have a Map object Map<t1, Set<t2>>, and I want to go into the set and turn t2 in the sets into the keys of the new map. The original key t1 will be the new value of the map.
For example, given a map containing two entries

{key1: [a, b, c], key2: [c, d]}

The resulting map would be

{a: [key1], b: [key1], c: [key1, key2], d: [key2]}

[ ] denotes Set in the above examples.

4 Answers

Java 8:

map.entrySet()
        .stream()
        .flatMap(e -> e.getValue()
                .stream()
                .map(v -> new SimpleEntry<>(v, e.getKey())))
        .collect(Collectors.groupingBy(Entry::getKey,
                Collectors.mapping(Entry::getValue, Collectors.toSet())))

Guava:

Multimaps.asMap(map.entrySet()
        .stream()
        .collect(ImmutableSetMultimap.flatteningToImmutableSetMultimap(
                Entry::getKey, e -> e.getValue().stream()))
        .inverse())

StreamEx:

EntryStream.of(map)
        .flatMapValues(Set::stream)
        .invert()
        .grouping(Collectors.toSet())

One way could be :

private static <T1,T2> Map<T1, Set<T2>> invertMap(Map<T2, Set<T1>> data) {
        Map<T1, Set<T2>> output = data.entrySet().stream().collect(() -> new HashMap<T1, Set<T2>>(),
                (mapLeft, leftEntry) -> {
                    for (T1 i : leftEntry.getValue()) {

                        Set<T2> values = mapLeft.get(i);
                        if (values == null)
                            values = new HashSet<>();

                        values.add(leftEntry.getKey());
                        mapLeft.put(i, values);
                    }
                }, (mapLeft, mapRight) -> mapLeft.putAll(mapRight));
        return output;
    }

One way to do it could be -

Map<V1,Set<V2>> inputHashMap = new HashMap<>(); // initialized with your input
Map<V2,Set<V1>> outputHashMap = new HashMap<>();

inputHashMap.forEach((val, keys) -> keys.forEach(key -> {
    if (outputHashMap.containsKey(key)) {
        outputHashMap.get(key).add(val);
    } else {
        outputHashMap.put(key, new HashSet<>() {{
            add(val);
        }});
    }
}));

Using stream (might be good for parallel processing using parallel() on stream)

Map<String, Set<String>> inMap = new HashMap<>();
inMap.put("key1", new HashSet<>(Arrays.asList("a", "b", "c")));
inMap.put("key2", new HashSet<>(Arrays.asList("c", "d")));

Map<String, Set<String>> outMap = inMap.entrySet().stream().collect(
    HashMap::new,
    (m, e) -> e.getValue().forEach(v -> m.computeIfAbsent(v, ignore -> new HashSet<>())
                                         .add(e.getKey())),
    (m1, m2) -> m2.forEach((key, value) -> m1.merge(key, value,
                                           (s1, s2) -> { s1.addAll(s2); return s1; })));

System.out.println(outMap);
// {a=[key1], b=[key1], c=[key1, key2], d=[key2]}

Of course old school for loop is much more cleaner

Map<String, Set<String>> outMap = new HashMap<>();

for (Entry<String, Set<String>> e : inMap.entrySet())
  for (String v : e.getValue())
    outMap.computeIfAbsent(v, key -> new HashSet<>()).add(e.getKey());

Less LOC

Map<String, Set<String>> outMap = new HashMap<>();
inMap.forEach((k, v) -> v.forEach(e ->
  outMap.computeIfAbsent(e, __ -> new HashSet<>()).add(k)));
Related