【问题标题】:Calculate weighted average with Java 8 streams使用 Java 8 流计算加权平均值
【发布时间】:2020-11-19 22:13:40
【问题描述】:

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

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

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

【问题讨论】:

标签: java java-8 java-stream


【解决方案1】:

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

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

示例用法如下:

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

【讨论】:

  • 感谢您的精彩解释。我将阅读有关自定义收集器的更多信息。
【解决方案2】:

您可以使用此过程来计算地图的加权平均值。注意map entry的key应该包含value,map entry的value应该包含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;
    }

您可以查看其单元测试以查看用例。

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

您需要这些导入来进行单元测试:

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

代码需要这些导入:

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

希望对你有帮助。

【讨论】:

    【解决方案3】:
    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;
    }
    

    【讨论】:

      【解决方案4】:
      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;
      }
      

      【讨论】:

      • 在回答一个老问题时,如果您包含一些上下文来解释您的答案如何提供帮助,那么您的答案将对其他 StackOverflow 用户更有用,特别是对于已经有一个已接受答案的问题。请参阅:How do I write a good answer
      猜你喜欢
      • 1970-01-01
      • 2011-02-12
      • 2019-02-27
      • 2021-11-24
      • 1970-01-01
      • 2010-10-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多