MiketheCalamity
MiketheCalamity

Reputation: 1269

Stream a collection of maps into a single map

I have a collection of maps. The internal maps can have keys that match, so I'd like to turn it into a Map of Collection:

Collection<Map<String, Thing>> => Map<String, Collection<Thing>>

What I've tried was:

Map<String, Collection<Thing>> newMap = oldCollection
    .stream()
    .map(Map::entrySet)
    .collect(Collectors.groupingBy(
             Entry::getKey,
             Collectors.mapping(Entry::getValue, Collectors.toList())));

I feel as though this should work but I get a compilation error:

Type mismatch: cannot convert from Map<Object,List<Object>> to Map<String,Collection<Thing>>

Does anyone know what I'm doing wrong?

Upvotes: 2

Views: 4963

Answers (4)

Bohemian
Bohemian

Reputation: 425073

It could be simpler to create the map first:

Map<String, Collection<Thing>> map = new HashMap<>();

oldCollection.stream()
    .map(Map::entrySet)
    .flatMap(Set::stream)
    .forEach(e -> map.computeIfAbsent(e.getKey(), k -> new ArrayList<>()).add(e.getValue()));

Upvotes: 0

Ravindra Ranwala
Ravindra Ranwala

Reputation: 21124

Alternatively you can do it like so,

final Map<String, List<Thing>> thingsMap = oldCollection.stream()
        .map(Map::entrySet)
        .flatMap(Set::stream)
        .collect(Collectors.groupingBy(
            Map.Entry::getKey,
            Collectors.mapping(
                Map.Entry::getValue,  
                Collectors.toList()
            )
        ));

You can get the entry set of each map, there you get a set of Sets, and then you flatten them to get one large Stream out of them. Finally you process that large stream.

Upvotes: 1

Holger
Holger

Reputation: 298233

For comparison, this is the loop solution:

Map<String, Collection<Thing>> newMap = new HashMap<>();
for(Map<String, Thing> m: oldCollection)
    m.forEach((s,t) -> newMap.computeIfAbsent(s, x->new ArrayList<>()).add(t));

You could express the same logic as Stream operation:

Map<String, Collection<Thing>> newMap = oldCollection
    .stream()
    .collect(HashMap::new,
             (r,m)->m.forEach((s,t)->r.computeIfAbsent(s,x->new ArrayList<>()).add(t)),
             (r,m)->m.forEach((s,l)->r.computeIfAbsent(s,x->new ArrayList<>()).addAll(l)));

This is what the other solution also do, except that the logic of flatMap has been integrated into the collector.

Upvotes: 1

Eran
Eran

Reputation: 393846

oldCollection.stream().map(Map::entrySet) creates a Stream<Set<Map.Entry<String,Thing>>>, but you need a Stream<Map.Entry<String,Thing>>.

Therefore you need to use flatMap:

Map<String, List<Thing>>
    newMap = oldCollection.stream()
                          .flatMap(m->m.entrySet().stream())
                          .collect(Collectors.groupingBy(Map.Entry::getKey, 
                                                         Collectors.mapping(Map.Entry::getValue,
                                                                            Collectors.toList())));

Also, since you are mapping the grouped values to a List, the output type should be Map<String, List<Thing>>.

You can change Collectors.toList() to Collectors.toCollection(ArrayList::new) if you want to keep the current output type.

Upvotes: 4

Related Questions