Using Java Streams to group together a List of objects by an attribute and reduce them to a new list of object with the average of another attribute

Marcello :

I have a list of SensorSample POJOs

public class SensorSample {

    private Device.SensorType sensorType; // This is an enum
    private double sample;
    private long timestamp;

    // Constructor

    // Setters

    // Getters

}

I need to group them by the timestamp, so that all the SensorSamples of the same day are together. Then I need to reduce them so that I have only one SensorSample for each day and the value of its sample is the average of the value of the sample of all the objects for that day. Is there a way to do that with Streams?

So far I got this to group them together:

Map<Long, List<SensorSample>> yearSamples = samples.stream()
                .collect(groupingBy(sample -> SECONDS_IN_A_DAY*Math.floorDiv(sample.getTimestamp(), SECONDS_IN_A_DAY)));

But I don't know how to go any further.

Ousmane D. :

it seems as if you want a List<SensorSample> as a result where each group after the groupingBy is reduced into a single SensorSample.

List<SensorSample> result = samples.stream()
                .collect(groupingBy(sample -> SECONDS_IN_A_DAY*Math.floorDiv(sample.getTimestamp(), SECONDS_IN_A_DAY))
                .entrySet()
                .stream()
                .map(e -> {
                    SensorSample sensorSample = new SensorSample();
                    sensorSample.setTimestamp(e.getKey());
                    double average = e.getValue().stream()
                            .mapToDouble(SensorSample::getSample)
                            .average().orElse(0);
                    sensorSample.setSample(average);
                    sensorSample.setSensorType(e.getValue().get(0).getSensorType());
                    return sensorSample;
                }).collect(Collectors.toList());

The map logic seems a little bit large thus I'd consider refactoring it out to a method as such:

private static SensorSample apply(Map.Entry<Long, List<SensorSample>> e) {
        SensorSample sensorSample = new SensorSample();
        sensorSample.setTimestamp(e.getKey());
        double average = e.getValue().stream()
                .mapToDouble(SensorSample::getSample)
                .average().orElse(0);
        sensorSample.setSample(average);
        sensorSample.setSensorType(e.getValue().get(0).getSensorType());
        return sensorSample;
}

Then the stream pipeline would become:

List<SensorSample> result = samples.stream()
                .collect(groupingBy(sample -> SECONDS_IN_A_DAY*Math.floorDiv(sample.getTimestamp(), SECONDS_IN_A_DAY))
                .entrySet()
                .stream()
                .map(Main::apply)
                .collect(Collectors.toList());

Where Main is the class containing the apply method.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=99505&siteId=1