简体   繁体   English

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

[英]Calculate weighted average with Java 8 streams

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.我如何计算Map<Double, Integer>加权平均值,其中 Integer 值是要平均的 Double 值的权重。 eg: Map has following elements:例如:地图具有以下元素:

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

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.我希望使用 Java 8 流应用以下公式,但不确定如何一起计算分子和分母并同时保留它。 How to use reduction here?这里如何使用reduce?

在此处输入图片说明

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

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 ).这个自定义收集器采用两个函数作为参数:一个函数返回用于给定流元素的值(作为ToDoubleFunction ),另一个返回权重(作为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 .完成者然后将两者的除法作为Double

A sample usage would be:示例用法是:

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

You can use this procedure to calculate the weighted average of a map.您可以使用此过程来计算地图的加权平均值。 Note that the key of the map entry should contain the value and the value of the map entry should contain the weight.请注意,映射条目的键应包含值,映射条目的值应包含权重。

     /**
     * Calculates the weighted average of a map.
     *
     * @throws ArithmeticException If divide by zero happens
     * @param map A map of values and weights
     * @return The weighted average of the map
     */
    static Double calculateWeightedAverage(Map<Double, Integer> map) throws ArithmeticException {
        double num = 0;
        double denom = 0;
        for (Map.Entry<Double, Integer> entry : map.entrySet()) {
            num += entry.getKey() * entry.getValue();
            denom += entry.getValue();
        }

        return num / denom;
    }

You can look at its unit test to see a usecase.您可以查看其单元测试以查看用例。

     /**
     * Tests our method to calculate the weighted average.
     */
    @Test
    public void testAveragingWeighted() {
        Map<Double, Integer> map = new HashMap<>();
        map.put(0.7, 100);
        map.put(0.5, 200);
        Double weightedAverage = calculateWeightedAverage(map);
        Assert.assertTrue(weightedAverage.equals(0.5666666666666667));
    }

You need these imports for the unit tests:您需要这些导入进行单元测试:

import org.junit.Assert;
import org.junit.Test;

You need these imports for the code:您需要这些代码导入:

import java.util.HashMap;
import java.util.Map;

I hope it helps.我希望它有帮助。

static float weightedMean(List<Double> value, List<Integer> weighted, int n) {
    int sum = 0;
    double numWeight = 0;

    for (int i = 0; i < n; i++) {
        numWeight = numWeight + value.get(i).doubleValue() * weighted.get(i).intValue();
        sum = sum + weighted.get(i).intValue();
    }

    return (float) (numWeight) / sum;
}
public static double weightedAvg(Collection<Map.Entry<? extends Number, ? extends Number> data) {
    var sumWeights = data.stream()
        .map(Map.Entry::getKey)
        .mapToDouble(Number::doubleValue)
        .sum();
    var sumData = data.stream()
        .mapToDouble(e -> e.getKey().doubleValue() * e.getValue().doubleValue())
        .sum();
    return sumData / sumWeights;
}

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

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