如何使用Java 8 lambda按顺序计算多个数字的平均值

mad*_*hub 11 java java-8

如果我有集合Point,如何在单次迭代中使用Java 8流计算x,y的平均值.

下面的示例在输入集合上创建两个流和迭代两次,以计算x和y的平均值.他们用计算机平均x,y在使用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();
Run Code Online (Sandbox Code Playgroud)

Bri*_*etz 7

编写一个平凡的收集器。看averagingInt收集器的实现(来自Collectors.java):

public static <T> Collector<T, ?, Double>
averagingInt(ToIntFunction<? super T> mapper) {
    return new CollectorImpl<>(
            () -> new long[2],
            (a, t) -> { a[0] += mapper.applyAsInt(t); a[1]++; },
            (a, b) -> { a[0] += b[0]; a[1] += b[1]; return a; },
            a -> (a[1] == 0) ? 0.0d : (double) a[0] / a[1], CH_NOID);
}
Run Code Online (Sandbox Code Playgroud)

这可以很容易地适应沿两个轴而不是一个轴求和(在单次传递中),并在一些简单的持有者中返回结果:

AverageHolder h = streamOfPoints.collect(averagingPoints());
Run Code Online (Sandbox Code Playgroud)

  • Java 7 风格(命令式)对我来说看起来更简单,如果您还想使用并行流并行运行平均,您的解决方案可能很好。`// java 7 float xSum = 0; 浮动 ySum = 0; for (Point2D.Float float1 : points) { xSum += float1.x; xSum += float1.y; } float xAvg = xSum / points.size(); 浮动 yAvg = ySum / points.size(); ` (2认同)

Luk*_*der 7

如果你不介意使用额外的库,我们最近已经为jOOλ添加了对元组收集器的支持.

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

在上面的代码中,Tuple.collectors()将多个java.util.stream.Collector实例组合成一个Collector将单个值收集到一个中的实例Tuple.

这比任何其他解决方案都更简洁和可重用.你需要支付的价格是,它目前在包装类型上运行,而不是原始类型double.我想我们将不得不等到Java 10并将valhalla项目用于泛型中的原始类型专业化.

如果您想要自己滚动,而不是创建依赖项,相关方法如下所示:

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)
        )
    );
}
Run Code Online (Sandbox Code Playgroud)

哪里Tuple2只是两个值的简单包装器.你不妨使用AbstractMap.SimpleImmutableEntry或类似的东西.

我还在回答另一个Stack Overflow问题时详细介绍了这种技术.


Ale*_* C. 4

一种方法是定义一个聚合点的 x 和 y 值的类。

public class AggregatePoints {

    private long count = 0L;
    private double sumX = 0;
    private double sumY = 0;

    public double averageX() { 
        return sumX / count; 
    }

    public double averageY() { 
        return sumY / count; 
    }

    public void merge(AggregatePoints other) {
      count += other.count;
      sumX += other.sumX;
      sumY += other.sumY;
    }

    public void add(Point2D.Float point) {
      count += 1;
      sumX += point.getX();
      sumY += point.getY();
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你只需将其收集Stream到一个新实例中:

 AggregatePoints agg = points.stream().collect(AggregatePoints::new,
                                               AggregatePoints::add,
                                               AggregatePoints::merge);
 double xAvg = agg.averageX();
 double yAvg = agg.averageY();
Run Code Online (Sandbox Code Playgroud)

尽管在列表上迭代两次是一个简单的解决方案。除非我真的有性能问题,否则我会这样做。