我正在尝试学习使用 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/22 + 1/32 + ... ) = ζ(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;
}
跟踪表明在看到最后一项时确实发生了一些事情,但没有什么好处:在一种情况下,计算停止但程序仍然挂起,在另一种情况下,它给了我一个可爱的小崩溃转储,我得到了向 Oracle 报告。
那么有什么方法可以完成我正在寻找的那种东西吗?
(注意:我现在假设串行流。但我认为这是可以从并行性中受益的问题,一旦我弄清楚如何让它工作。)
最佳答案
如果终止条件和 Collector
的结果之间存在这种依赖关系,则使用可变状态是不可避免的。但只要不需要并行执行,实现就可以很简单:
class MutableDouble {
double sum;
}
MutableDouble sumHolder=new MutableDouble();
DoubleStream ds=IntStream.iterate(1, (i -> i + 1))
.mapToDouble(n -> 1 / ((double)n * n));
ds.peek(term -> sumHolder.sum+=term)
.filter(term -> Math.abs(term)<1e-12*Math.abs(sumHolder.sum))
.findFirst();
System.out.println(sumHolder.sum);
由于它会在比较之前进行总结,因此它与您的原始逻辑并不完全相同,而是像:
double sum = 0;
for (double n = 1; ; n++) {
double term = 1 / (n * n);
sum += term;
if (Math.abs(term) <= 1e-12 * Math.abs(sum)) {
break;
}
}
如果你坚持原来的逻辑,它必须稍微复杂一点:
class MutableDouble {
double sum, next;
void add(double d) {
sum=next;
next+=d;
}
}
MutableDouble sumHolder=new MutableDouble();
DoubleStream ds=IntStream.iterate(1, (i -> i + 1))
.mapToDouble(n -> 1 / ((double)n * n));
ds.peek(sumHolder::add)
.filter(term -> Math.abs(term)<1e-12*Math.abs(sumHolder.sum))
.findFirst();
关于java - 使用流计算无穷和,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22822830/