繁体   English   中英

计算对象的加权平均值 Java stream

[英]Calculate weighted average from objects with Java stream

给定一个简单的Measurement值 class:

@Getter
@AllArgsConstructor
public class Measurement {
    private BigDecimal rawValue;
    private BigDecimal weighting;
}

该服务处理List<Measurement>并应通过应用以下步骤计算加权平均值:

  1. 将每个rawValue与其weighting相乘。
  2. 从 (1) 构建所有加权原始值的总和。
  3. 将总和 (2) 除以所有权重的总和。

例子

(1.0 * 5.0) + (2.0 * 2.0) + (3.0 * 7.0) / 14

这是我当前的实现:

public class MeasurementService {

    private List<Measurement> measurements = new ArrayList<>();

    public MeasurementService() {
        Measurement m1 = new Measurement(new BigDecimal("1.0"), new BigDecimal("5.0"));
        Measurement m2 = new Measurement(new BigDecimal("2.0"), new BigDecimal("2.0"));
        Measurement m3 = new Measurement(new BigDecimal("3.0"), new BigDecimal("7.0"));
        measurements.add(m1);
        measurements.add(m2);
        measurements.add(m3);
    }

    public BigDecimal calculateWeightedAverage() {
        final BigDecimal totalWeighting = measurements.stream()
                .map(measurement -> measurement.getWeighting())
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        return measurements.stream()
                .map(measurement -> measurement.getRawValue().multiply(measurement.getWeighting()))
                .reduce(BigDecimal.ZERO, BigDecimal::add)
                .divide(totalWeighting, 3, RoundingMode.DOWN);
    }
}

虽然这是有效的,但我不喜欢两次使用 stream measurements ,我敢打赌有更好的方法。 不幸的是,到目前为止我还想不出一个。

您知道如何简化calculateWeightedAverage方法吗?

我正在使用 Java 17。

假设您使用的是 Java 12 或更高版本,为了避免 stream 超过列表两次,您可以使用发球收集器。 除了这里的文档之外,还有一篇关于它的文章the-teeing-collector

public BigDecimal calculateWeightedAverage() {
    return measurements.stream()
                .collect(Collectors.teeing(
                        Collectors.reducing(BigDecimal.ZERO, m -> m.getRawValue().multiply(m.getWeighting()), BigDecimal::add),
                        Collectors.reducing(BigDecimal.ZERO, Measurement::getWeighting, BigDecimal::add ),
                        (weightedRawValues , totalWeighting) -> weightedRawValues.divide(totalWeighting, 3, RoundingMode.DOWN)));
}

Map 到一个新的 class 并将其减少,然后使用减少的值来计算最终结果。 这适用于 Java 8+。 就像是:

@Data
@RequiredArgsConstructor
public class ResultAccumulator {
    public static final ResultAccumulator ZERO = new ResultAccumulator(BigDecimal.ZERO, BigDecimal.ZERO);

    private final BigDecimal weightedValue;
    private final BigDecimal weightSum;

    public static ResultAccumulator fromMeasurement(final Measurement measurement) {
        return new ResultAccumulator(
                measurement.getRawValue().multiply(measurement.getWeighting()),
                measurement.getWeighting());
    }

    public ResultAccumulator merge(final ResultAccumulator other) {
        return new ResultAccumulator(
                getWeightedValue().add(other.getWeightedValue()),
                getWeightSum().add(other.getWeightSum()));
    }

    public BigDecimal getFinalResult() {
        return getWeightedValue().divide(getWeightSum(), 3, RoundingMode.DOWN);
    }
}

public BigDecimal calculateWeightedAverage() {
    final ResultAccumulator result = measurements.stream()
            .map(ResultAccumulator::fromMeasurement)
            .reduce(
                    ResultAccumulator.ZERO,
                    ResultAccumulator::merge);
    return result.getFinalResult();
}

或者通过将“加权值”存储在rawValue中并将权重总和存储在weight中来快速而肮脏地进行操作并减少原始Measurement值。 两个类的结构是相同的,但是使用两个类在 2 周内对他人和你自己都不会造成混淆。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM