如果我有集合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)
编写一个平凡的收集器。看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)
如果你不介意使用额外的库,我们最近已经为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问题时详细介绍了这种技术.
一种方法是定义一个聚合点的 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)
尽管在列表上迭代两次是一个简单的解决方案。除非我真的有性能问题,否则我会这样做。
| 归档时间: |
|
| 查看次数: |
7667 次 |
| 最近记录: |