Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java 8 stream aggregate a map

Im trying to do something seemingly straighforward but with no luck so far. I have a list of Map, they come from a bunch of CompletableFutures in parallel, the result then has to be joined. Since the key in the map is unique, In need to merge the values and produce a new map of unique keys associated with the combined list of SomeType. For example:

Map1: key1 : Sometype1 key2 : Sometype2

Map2 key1 : Sometype3 key2 : Sometype4

Desired final result: Map3: key1 : Sometype, Sometype3 key2 : Sometype2, Sometype4

I tried using groupBy and other methods, but it seems most of them assume working with a List and not a Map to begin with so I wasn't able to find a straightforward way to do it.

So far I have something like:

 Map<String, List<Sometype>> volPercent = waitGroup.stream()
            .map(CompletableFuture::join)
            .flatMap((e) -> e.entrySet().stream())
            .collect(Collectors.groupingBy());

The part I'm not sure is what needs to go in the grouping by part. I tried Map.Entry::getKey, but it doesn't compile:

Error:(69, 25) java: incompatible types: inference variable T has incompatible bounds
equality constraints: com.Sometype
lower bounds: java.util.Map.Entry<java.lang.String,com.Sometype>
like image 631
Feras Avatar asked Feb 19 '15 04:02

Feras


2 Answers

I'll assume that you're starting with a stream of maps:

Stream<Map<String, Sometype>> input = ... ;

You got pretty far with the flatmapping and collecting. However, the code you tried,

    input.flatMap(map -> map.entrySet().stream())
         .collect(Collectors.groupingBy(Map.Entry::getKey));

gives you a map whose keys are String and whose values are a list of map entries, specifically List<Map.Entry<String,Sometype>>, not a List<Sometype> as you wanted. What you have to do is use the "downstream" feature of collectors after you've grouped them by key. You want to take each Map.Entry and extract (map) it to its value, which is Sometype. This is done with a mapping collector.

Now you potentially have several instances of Sometype for each key, so you need to collect them into a list. This is done using another downstream collector, which collects them into a list. This is done using the familiar toList() collector.

The final code looks like this:

Map<String, List<Sometype>> result =
    input.flatMap(map -> map.entrySet().stream())
         .collect(groupingBy(Map.Entry::getKey,
                             mapping(Map.Entry::getValue,
                                     toList())));
like image 165
Stuart Marks Avatar answered Sep 19 '22 08:09

Stuart Marks


The problem is that your initial attempt (as I understand it)

Collectors.groupingBy(Map.Entry::getKey)

collects to a Map<K, List<Map.Entry<K, V>>>.

After the flatMap, you have a Stream<Map.Entry<K, V>> whose elements groupingBy maps to K.

But you want a Map<K, List<V>> so you need to use the groupingBy that takes a downstream collector, not just a classifier, so you can also map Map.Entry<K, V> to V.

Something like

Collectors.groupingBy(
    Map.Entry::getKey,
    Collectors.mapping(
        Map.Entry::getValue,
        Collectors.toList()
    )
)
like image 45
Radiodef Avatar answered Sep 18 '22 08:09

Radiodef