Multiple Aggregate Functions in Java 8 Stream API

I have a class defined as

public class TimePeriodCalc { private double occupancy; private double efficiency; private String atDate; } 

I would like to execute the following SQL statement using the Java 8 Stream API.

 SELECT atDate, AVG(occupancy), AVG(efficiency) FROM TimePeriodCalc GROUP BY atDate 

I tried:

 Collection<TimePeriodCalc> collector = result.stream().collect(groupingBy(p -> p.getAtDate(), .... 

What can be added to the code to select multiple attributes? I’m thinking of using several collectors, but I don’t really know how to do it.

+7
java java-8 java-stream
source share
4 answers

To do this without a custom Collector (without repeating the result again), you can do it like this. It is a little dirty, since it first collects Map<String, List<TimePeriodCalc>> , and then passes this list and gets the middle double.

Since you need two averages, they are collected in Holder or Pair , in this case I use AbstractMap.SimpleEntry

  Map<String, SimpleEntry<Double, Double>> map = Stream.of(new TimePeriodCalc(12d, 10d, "A"), new TimePeriodCalc(2d, 16d, "A")) .collect(Collectors.groupingBy(TimePeriodCalc::getAtDate, Collectors.collectingAndThen(Collectors.toList(), list -> { double occupancy = list.stream().collect( Collectors.averagingDouble(TimePeriodCalc::getOccupancy)); double efficiency = list.stream().collect( Collectors.averagingDouble(TimePeriodCalc::getEfficiency)); return new AbstractMap.SimpleEntry<>(occupancy, efficiency); }))); System.out.println(map); 
+4
source share

Assuming your TimePeriodCalc class has all the necessary getters, it should get the list you want:

 List<TimePeriodCalc> result = new ArrayList<>( list.stream() .collect(Collectors.groupingBy(TimePeriodCalc::getAtDate, Collectors.collectingAndThen(Collectors.toList(), TimePeriodCalc::avgTimePeriodCalc))) .values() ); 

Where TimePeriodCalc.avgTimePeriodCalc is this method in the TimePeriodCalc class:

 public static TimePeriodCalc avgTimePeriodCalc(List<TimePeriodCalc> list){ return new TimePeriodCalc( list.stream().collect(Collectors.averagingDouble(TimePeriodCalc::getOccupancy)), list.stream().collect(Collectors.averagingDouble(TimePeriodCalc::getEfficiency)), list.get(0).getAtDate() ); } 

The above can be combined into this monster:

 List<TimePeriodCalc> result = new ArrayList<>( list.stream() .collect(Collectors.groupingBy(TimePeriodCalc::getAtDate, Collectors.collectingAndThen( Collectors.toList(), a -> { return new TimePeriodCalc( a.stream().collect(Collectors.averagingDouble(TimePeriodCalc::getOccupancy)), a.stream().collect(Collectors.averagingDouble(TimePeriodCalc::getEfficiency)), a.get(0).getAtDate() ); } ))) .values()); 

When entering:

 List<TimePeriodCalc> list = new ArrayList<>(); list.add(new TimePeriodCalc(10,10,"a")); list.add(new TimePeriodCalc(10,10,"b")); list.add(new TimePeriodCalc(10,10,"c")); list.add(new TimePeriodCalc(5,5,"a")); list.add(new TimePeriodCalc(0,0,"b")); 

This will give:

 TimePeriodCalc [occupancy=7.5, efficiency=7.5, atDate=a] TimePeriodCalc [occupancy=5.0, efficiency=5.0, atDate=b] TimePeriodCalc [occupancy=10.0, efficiency=10.0, atDate=c] 
+3
source share

Here is the path with a custom collector. He needs only one pass, but it's not very simple, especially because of the generics ...

If you have this method:

 @SuppressWarnings("unchecked") @SafeVarargs static <T, A, C extends Collector<T, A, Double>> Collector<T, ?, List<Double>> averagingManyDoubles(ToDoubleFunction<? super T>... extractors) { List<C> collectors = Arrays.stream(extractors) .map(extractor -> (C) Collectors.averagingDouble(extractor)) .collect(Collectors.toList()); class Acc { List<A> averages = collectors.stream() .map(c -> c.supplier().get()) .collect(Collectors.toList()); void add(T elem) { IntStream.range(0, extractors.length).forEach(i -> collectors.get(i).accumulator().accept(averages.get(i), elem)); } Acc merge(Acc another) { IntStream.range(0, extractors.length).forEach(i -> averages.set(i, collectors.get(i).combiner() .apply(averages.get(i), another.averages.get(i)))); return this; } List<Double> finish() { return IntStream.range(0, extractors.length) .mapToObj(i -> collectors.get(i).finisher().apply(averages.get(i))) .collect(Collectors.toList()); } } return Collector.of(Acc::new, Acc::add, Acc::merge, Acc::finish); } 

Gets an array of functions that retrieves double values ​​from each element of the stream. These extractors are converted to Collectors.averagingDouble Collectors.averagingDouble , and then a local Acc class is created with mutable structures that are used to accumulate average values ​​for each collector. Then the battery function goes to each battery, and therefore with the functions of the combiner and finisher.

Usage is as follows:

 Map<String, List<Double>> averages = list.stream() .collect(Collectors.groupingBy( TimePeriodCalc::getAtDate, averagingManyDoubles( TimePeriodCalc::getOccupancy, TimePeriodCalc::getEfficiency))); 
+1
source share

You can associate several attributes as follows:

 Collection<TimePeriodCalc> collector = result.stream().collect(Collectors.groupingBy(p -> p.getAtDate(), Collectors.averagingInt(p -> p.getOccupancy()))); 

If you want more, you get this idea.

0
source share

All Articles