使用 Java 8 流计算加权平均值

2022-01-22 00:00:00 java-8 java java-stream

如何计算 Map 的加权平均值,其中 Integer 值是要平均的 Double 值的权重.例如:地图有以下元素:

How do I go about calculating weighted mean of a Map<Double, Integer> where the Integer value is the weight for the Double value to be averaged. eg: Map has following elements:

  1. (0.7, 100)//值为 0.7,权重为 100
  2. (0.5, 200)
  3. (0.3, 300)
  4. (0.0, 400)

我希望使用 Java 8 流应用以下公式,但不确定如何同时计算分子和分母并同时保留它.这里如何使用reduction?

I am looking to apply the following formula using Java 8 streams, but unsure how to calculate the numerator and denominator together and preserve it at the same time. How to use reduction here?

推荐答案

您可以为此任务创建自己的收集器:

You can create your own collector for this task:

static <T> Collector<T,?,Double> averagingWeighted(ToDoubleFunction<T> valueFunction, ToIntFunction<T> weightFunction) {
    class Box {
        double num = 0;
        long denom = 0;
    }
    return Collector.of(
             Box::new,
             (b, e) -> { 
                 b.num += valueFunction.applyAsDouble(e) * weightFunction.applyAsInt(e); 
                 b.denom += weightFunction.applyAsInt(e);
             },
             (b1, b2) -> { b1.num += b2.num; b1.denom += b2.denom; return b1; },
             b -> b.num / b.denom
           );
}

此自定义收集器采用两个函数作为参数:一个是返回值以用于给定流元素的函数(作为 ToDoubleFunction),另一个返回权重(作为 ToIntFunction).它使用一个辅助本地类在收集过程中存储分子和分母.每次接受一个条目时,分子会随着该值与其权重相乘的结果而增加,而分母会随着权重而增加.然后完成器将两者的除法返回为 Double.

This custom collector takes two functions as parameter: one is a function returning the value to use for a given stream element (as a ToDoubleFunction), and the other returns the weight (as a ToIntFunction). It uses a helper local class storing the numerator and denominator during the collecting process. Each time an entry is accepted, the numerator is increased with the result of multiplying the value with its weight, and the denominator is increased with the weight. The finisher then returns the division of the two as a Double.

示例用法如下:

Map<Double,Integer> map = new HashMap<>();
map.put(0.7, 100);
map.put(0.5, 200);

double weightedAverage =
  map.entrySet().stream().collect(averagingWeighted(Map.Entry::getKey, Map.Entry::getValue));

相关文章