Ankur
Ankur

Reputation: 477

How to convert Stream of Maps into a single Map

I need to use streams to convert the Stream<Map<EntityType, Set<String>>> to Map<EntityType, Set<String>>.

The EntityType is an enum having certain values, let's say A, B and C. And there are lots of maps in the stream.

I want to concatenate all of them into one map using Stream API. Is there any way?

Upvotes: 1

Views: 738

Answers (2)

Alexander Ivanchenko
Alexander Ivanchenko

Reputation: 28988

For that, you can flatten the maps in the stream by applying flatMap(). And wrap each entry with a new one. This step is required 1. to avoid mutation of the source and 2. to prevent an UnsupportedOperationException which will be raised if sets are unmodifiable.

And then apply collect().

Since EntityType is an enum the most appropriate choice for the container provided by the supplier will be an EnumMap which was designed specifically for enum-keys and has a better performance than HashMap.

Inside the combine method merge() is being applied on the resulting map to add entries from the stream into it.

public static <T extends Enum<T>, U> Map<T, Set<U>> collect(Stream<Map<T, Set<U>>> source,
                                                            Class<T> enumClass) {
    return source
            .flatMap(map -> map.entrySet().stream()
                    .map(entry -> Map.entry(entry.getKey(), new HashSet<>(entry.getValue()))))
            .collect(() -> new EnumMap<>(enumClass),
                    (map, entry) -> map.merge(entry.getKey(),
                                              entry.getValue(),
                                              (v1, v2) -> {v1.addAll(v2); return v1;}),
                    Map::putAll);
}

main()

public static void main(String[] args) {
    Stream<Map<EntityType, Set<String>>> source =
            Stream.of(Map.of(EntityType.A, Set.of("foo"), EntityType.B, Set.of("bar")),
                    Map.of(EntityType.A, Set.of("foo", "bar"), EntityType.B, Set.of("bar", "fiz")),
                    Map.of(EntityType.A, Set.of("foobar"), EntityType.B, Set.of("abc", "www")));

    System.out.println(collect(source, EntityType.class));
}

Output

{A=[bar, foobar, foo], B=[bar, abc, fiz, www]}

Upvotes: 1

Christos
Christos

Reputation: 53958

You could try something like this:

public static void main(String[] args) {

    Map<EntityType, Set<String>> mapA = Map.of(EntityType.A, Set.of("1", "2", "3"), EntityType.B, Set.of("4", "5", "6"));
    Map<EntityType, Set<String>> mapB = Map.of(EntityType.A, Set.of("1", "4", "5"), EntityType.C, Set.of("4", "5", "6"));
    Map<EntityType, Set<String>> mapC = Map.of(EntityType.A, Set.of("3", "7", "5"), EntityType.B, Set.of("4", "9", "6"));
    
    // The stream calls you can try:
    Map<EntityType, Set<String>> result = Stream.of(mapA, mapB, mapC)
      .flatMap(map -> map.entrySet().stream())
      .collect(Collectors.toMap(keyValuePair -> keyValuePair.getKey(),
                                keyValuePair -> keyValuePair.getValue(),
                                (value1, value2) -> combine(value1, value2)));
}

private static <T> Set<T> combine(Set<T>... sets) {
    return Stream.of(sets)
      .flatMap(Set::stream)
      .collect(Collectors.toSet());
}

enum EntityType {
    A,
    B,
    C
}

Upvotes: 3

Related Questions