【问题标题】:Averaging across multiple fields with IntSummaryStatistics使用 IntSummaryStatistics 跨多个字段求平均值
【发布时间】:2020-09-09 19:51:09
【问题描述】:

我正在尝试使用 Java 8 流创建单个 CarData 对象,该对象由来自 getCars 的列表中所有 CarData 字段的平均值组成;

   CarData = new CarData();
   CarData.getBodyWeight returns Integer
   CarData.getShellWeight returns Integer

     List<CarData> carData = carResults.getCars();
    
    IntSummaryStatistics averageBodyWeight = carData.stream()
            .mapToInt((x) -> x.getBodyWeight())
            .summaryStatistics();
    
    averageBodyWeight.getAverage(); 


            IntSummaryStatistics averageShellWeight = carData.stream()
            .mapToInt((x) -> x.getShellWeight())
            .summaryStatistics();
    
    getShellWeight.getAverage(); 

我不想在我的最终返回结果中将这些重新组合在一起。

从视觉上看,这是我的清单

getCars() : [
 {CarData: { getBodyWeight=10, getShellWeight=3 } }
 {CarData: { getBodyWeight=6, getShellWeight=5 } }
 {CarData: { getBodyWeight=8, getShellWeight=19 } }
]

我试图实现的输出是一个对象,它具有我指定的每个字段的平均值。不确定我是否需要使用 Collectors.averagingInt 或 IntSummaryStatistics 的一些组合来实现这一点。对于这些技术中的任何一种,跨一个字段都很容易做到,只是不确定在使用多个整数字段时我缺少什么。

 {CarData: { getBodyWeight=8, getShellWeight=9 } }

【问题讨论】:

  • 我认为流不提供此功能。您可以创建一个不同的收集器思想来创建平均 CarData 对象。

标签: java java-8 java-stream


【解决方案1】:

从 JDK 12 开始,您可以使用以下解决方案:

CarData average = carData.stream().collect(Collectors.teeing(
    Collectors.averagingInt(CarData::getBodyWeight),
    Collectors.averagingInt(CarData::getShellWeight),
    (avgBody, avgShell) -> new CarData(avgBody.intValue(), avgShell.intValue())));

对于较旧的 Java 版本,您可以执行以下任一操作,将 this answerteeing 实现添加到您的代码库并完全按照上述方式使用它,或者创建一个针对您的任务量身定制的自定义收集器,如 Andreas’ answer 所示。

或者考虑在内存中通过List 进行两次流式传输并不一定比在一个流中执行两个操作更糟糕,无论是可读性还是性能方面。

请注意,在 Double 对象上调用 intValue() 与 Andreas 回答中的 (int) 强制转换具有相同的行为。因此,无论哪种情况,如果需要其他舍入行为,您都必须调整代码。

或者您考虑使用不同的结果对象,能够为平均值保存两个浮点值。

【讨论】:

  • 天哪,真漂亮。不幸的是这里有 JDK 8,但很高兴知道。
【解决方案2】:

你需要自己写Collector,类似这样:

class CarDataAverage {
    public static Collector<CarData, CarDataAverage, Optional<CarData>> get() {
        return Collector.of(CarDataAverage::new, CarDataAverage::add,
                            CarDataAverage::combine,CarDataAverage::finish);
    }
    private long sumBodyWeight;
    private long sumShellWeight;
    private int count;
    private void add(CarData carData) {
        this.sumBodyWeight += carData.getBodyWeight();
        this.sumShellWeight += carData.getShellWeight();
        this.count++;
    }
    private CarDataAverage combine(CarDataAverage that) {
        this.sumBodyWeight += that.sumBodyWeight;
        this.sumShellWeight += that.sumShellWeight;
        this.count += that.count;
        return this;
    }
    private Optional<CarData> finish() {
        if (this.count == 0)
            return Optional.empty();
        // adjust as needed if averages should be rounded
        return Optional.of(new CarData((int) (this.sumBodyWeight / this.count),
                                       (int) (this.sumShellWeight / this.count)));
    }
}

然后你像这样使用它:

List<CarData> list = ...

Optional<CarData> averageCarData = list.stream().collect(CarDataAverage.get());

【讨论】:

    猜你喜欢
    • 2019-01-21
    • 2019-11-07
    • 1970-01-01
    • 1970-01-01
    • 2015-10-29
    • 1970-01-01
    • 2023-01-12
    • 2017-06-13
    • 2017-02-06
    相关资源
    最近更新 更多