使用Java Streams按属性将对象列表分组,并将它们缩减为新的对象列表,该列表包括另一个属性的平均值。

3

我有一个SensorSample POJO对象列表

public class SensorSample {

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

    // Constructor

    // Setters

    // Getters

}

我需要按照时间戳对它们进行分组,以便将同一天的所有SensorSample放在一起。然后我需要对它们进行缩减,以便每天只有一个SensorSample,其sample的值是当天所有对象的sample值的平均值。是否有一种使用流(Streams)的方法可以实现这一点?
到目前为止,我已经将它们分组在了一起:
Map<Long, List<SensorSample>> yearSamples = samples.stream()
                .collect(groupingBy(sample -> SECONDS_IN_A_DAY*Math.floorDiv(sample.getTimestamp(), SECONDS_IN_A_DAY)));

但是我不知道该如何继续前进。

1
你应该使用带有两个参数的 groupingBy 版本。第二个参数是你的第二个收集器,它执行归约操作。 - RealSkeptic
2个回答

2

我认为应该是这样的,要找到一组数字的平均数:

Map<Long, Double> averages = samples.stream()
  .collect(groupingBy(SensorSample::getTimestamp,
   averagingDouble(SensorSample::getSample)));

我没有展开你的日历公式,如果我只调用getTimestamp并省略细节,那么它会更易读。如果在SensorSample中添加一个getDay方法,你的代码也可以更易读。

此外,如果你提供了一个MCVE,测试就会更容易,因为仅凭一个部分类很难测试上述代码。


谢谢您的回答,但是那样我只能得到每组样本的平均值。相反,我需要一个新的SensorSample对象列表,每个对象都有某一天的平均值。 - Marcello

1
似乎您想要一个 List<SensorSample> 作为结果,在 groupingBy 后的每个组合中被简化为单个 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());

map的逻辑似乎有些庞大,因此我考虑将其重构为一个方法,如下:

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;
}

然后流水线将变成:
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());

其中Main是包含apply方法的类。


非常感谢!这解决了我的问题,正是我在寻找的!我会将其标记为正确答案。然而,我想指出我不能使用SensorSample::getTimestamp来分组样本,因为它们都有不同的时间戳。为此,我必须在我的问题中使用lambda函数来给属于特定日期的所有样本赋相同的时间戳。我知道这只是一个不太重要的问题,不会真正影响您的答案的正确性,但如果您能更好地匹配问题并进行更正,我会很感激。 - Marcello
@Marcello 好的,我会的。 - Ousmane D.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接