How to compute average of multiple numbers in sequence using Java 8 lambda

madhub picture madhub · Jan 13, 2015 · Viewed 8.3k times · Source

If I have collections Point , how do I compute average of x,y using Java 8 stream on a single iteration.

Following example creates two stream & iterates twice on the input collection to compute the average of x & y. Is their any way to computer average x,y on single iteration using java 8 lambda :

List<Point2D.Float> points = 
Arrays.asList(new Point2D.Float(10.0f,11.0f), new Point2D.Float(1.0f,2.9f));
// java 8, iterates twice
double xAvg = points.stream().mapToDouble( p -> p.x).average().getAsDouble();
double yAvg = points.stream().mapToDouble( p -> p.y).average().getAsDouble();

Answer

Lukas Eder picture Lukas Eder · Jan 30, 2015

If you don't mind using an additional library, we've added support for tuple collectors to jOOλ, recently.

Tuple2<Double, Double> avg = points.stream().collect(
    Tuple.collectors(
        Collectors.averagingDouble(p -> p.x),
        Collectors.averagingDouble(p -> p.y)
    )
);

In the above code, Tuple.collectors() combines several java.util.stream.Collector instances into a single Collector that collects individual values into a Tuple.

This is much more concise and reusable than any other solution. The price you'll pay is that this currently operates on wrapper types, instead of primitive double. I guess we'll have to wait until Java 10 and project valhalla for primitive type specialisation in generics.

In case you want to roll your own, instead of creating a dependency, the relevant method looks like this:

static <T, A1, A2, D1, D2> Collector<T, Tuple2<A1, A2>, Tuple2<D1, D2>> collectors(
    Collector<T, A1, D1> collector1
  , Collector<T, A2, D2> collector2
) {
    return Collector.of(
        () -> tuple(
            collector1.supplier().get()
          , collector2.supplier().get()
        ),
        (a, t) -> {
            collector1.accumulator().accept(a.v1, t);
            collector2.accumulator().accept(a.v2, t);
        },
        (a1, a2) -> tuple(
            collector1.combiner().apply(a1.v1, a2.v1)
          , collector2.combiner().apply(a1.v2, a2.v2)
        ),
        a -> tuple(
            collector1.finisher().apply(a.v1)
          , collector2.finisher().apply(a.v2)
        )
    );
}

Where Tuple2 is just a simple wrapper for two values. You might as well use AbstractMap.SimpleImmutableEntry or something similar.

I've also detailed this technique in an answer to another Stack Overflow question.