我正在尝试学习使用 Stream API 的详细信息,我给自己的一项任务是尝试编写一个方法,该方法采用无限DoubleStream
并尝试计算总和(假设它收敛)。也就是说,我想写一个方法
public static double infiniteSum(DoubleStream ds) { ... }
我可以用类似的东西打电话
double sum = infiniteSum(IntStream.iterate(1, (i -> i + 1))
.mapToDouble(n -> 1 / ((double)n * n)));
得到总和 (1 + 1/2 2 + 1/3 2 + ... ) = ζ(2) = π 2 /6。
我以旧方式执行此操作的粗略方法:
public static void yeOldeWaye() {
double sum = 0;
for (double n = 1; ; n++) {
double term = 1 / (n * n);
if (Math.abs(term) <= 1e-12 * Math.abs(sum)) {
break;
}
sum += term;
}
System.out.println(sum);
}
这给了我一个精确到 5 个位置的结果。
我可以使用以下方式以黑客方式实现该方法iterator()
:
public static double infiniteSum1(DoubleStream ds) {
double sum = 0;
PrimitiveIterator.OfDouble it = ds.iterator();
while (true) {
double term = it.next();
if (Math.abs(term) <= 1e-12 * Math.abs(sum)) {
break;
}
sum += term;
}
return sum;
}
但这感觉就像回到旧的方式,我一直在寻找一种方法,以更多地使用流的方式,或者其他方式。
这会产生正确的结果:
private static class DoubleAccumulator {
public double sum;
public DoubleAccumulator() {
sum = 0;
}
}
public static double infiniteSum(DoubleStream ds) {
DoubleAccumulator summer = ds.limit(800000).collect
(DoubleAccumulator::new,
(s, d) -> s.sum += d,
(s1, s2) -> s1.sum += s2.sum);
return summer.sum;
}
但我碰巧知道旧方法使用了近 800000 个术语,并且对流进行限制违背了我的目的。问题是我没有看到除使用之外的其他方法来切断流limit()
,这意味着我必须事先知道我将拥有多少个术语;我没有看到基于某些条件来停止流的方法,该条件是根据我在流中看到的内容计算得出的。
这不起作用:
public static double infiniteSum(DoubleStream ds) {
DoubleAccumulator summer = ds.collect
(DoubleAccumulator::new,
(s, d) -> { if (Math.abs(d) <= 1e-12 * Math.abs(s.sum)) {
ds.close(); // AAACK
} else
s.sum += d;
},
(s1, s2) -> s1.sum += s2.sum);
return summer.sum;
}
跟踪表明当看到最后一个术语时确实发生了一些事情,但没有什么好处:在一种情况下,计算停止但程序仍然挂起,在另一种情况下,它给了我一个可爱的小故障转储,我可以报告给甲骨文。
那么有没有办法完成我正在寻找的那种事情?
(注意:我现在假设串行流。但我认为这是可以从并行性中受益的问题,一旦我弄清楚如何使它工作。)