Vivek Sethi - 1 year ago 82

Java Question

How do I go about calculating weighted mean of a

`Map<Double, Integer>`

eg: Map has following elements:

- (0.7, 100) // value is 0.7 and weight is 100
- (0.5, 200)
- (0.3, 300)
- (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. How to use reduction here?

Answer Source

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`

). 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`

.

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