Java: Group By then Map
Asked Answered
E

3

6

I have a stream of Events

public class Event {
    Location location;
    double turnout;
    //... other fields & getters
}

And a statistics class EventStatistics

public class EventStatistics {
    // Stats properties e.g. turnout standard deviation/median

    public EventStatistics(List<Event> events) {
        // Generate stats
    }
}

I need to group all the events by location & create a map of location and event statistics Map<Location, EventStatistics>

The group by is just:

Map<Location, List<Event>> byLocation = events.stream().collect(groupingBy(Event::getLocation));

I know there is an overloaded groupingBy(function, collector) collector. Can I use somehow this to generate my Map<Location, EventStatistics> in a single stream?

Eleanor answered 10/3, 2018 at 8:20 Comment(0)
C
14

All you need is collectingAndThen:

Map<Location, EventStatistics> result = 
    events.stream()
          .collect(Collectors.groupingBy(Event::getLocation,
                                         Collectors.collectingAndThen(
                                             Collectors.toList(), 
                                             EventStatistics::new)));
Creation answered 10/3, 2018 at 8:38 Comment(0)
J
2

You can construct your own Collector using Collector.of(...), like this:

Map<Location, EventStatistics> collect = events.stream().collect(groupingBy(Event::getLocation,
        Collector.of(ArrayList::new,
                     List::add,
                     (left, right) -> { left.addAll(right); return left; },
                     EventStatistics::new)
));
Judon answered 10/3, 2018 at 8:38 Comment(1)
This code doesn't compile for me. ArrayList::new doesn't infer the Event type. I need to specify ArrayList<Event>::new.Wailful
A
1

If your EventStatistics were to be able to accept single Events instead of a full list, and a method to merge two statistics, as in

EventStatistics {
    public EventStatistics() {}
    public void addEvent(Event e);
    public EventStatistics merge(EventStatistics toMerge);
}

then you can do

groupingBy(Event::getLocation, Collector.of(EventStatistics::new, EventStatistics::accept, EventStatistics::merge));

Here, the argument-less constructor is the Supplier, the accept is the accumulator, and the merge is the combiner.

Armand answered 10/3, 2018 at 8:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.