使用流来计算无限总和

ajb*_*ajb 5 java java-8 java-stream

我正在尝试学习使用Stream API的细节,我给自己的一个任务是尝试编写一个无限的方法DoubleStream并尝试计算总和(假设它收敛).也就是说,我想写一个方法

public static double infiniteSum(DoubleStream ds) { ... }
Run Code Online (Sandbox Code Playgroud)

我可以用类似的东西打电话

double sum = infiniteSum(IntStream.iterate(1, (i -> i + 1))
                                  .mapToDouble(n -> 1 / ((double)n * n)));
Run Code Online (Sandbox Code Playgroud)

得到的总和(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);
}
Run Code Online (Sandbox Code Playgroud)

这给了我一个精确到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;
}
Run Code Online (Sandbox Code Playgroud)

但这只是感觉就像恢复旧的方式,我正在寻找一种方法,使用更多的方式使用流,或其他东西.

这会产生正确的结果:

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

但我碰巧知道旧的方法使用了将近80万个术语,并且对流的限制违背了我的目的.问题是我没有看到除了使用之外切断流的方法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;
}
Run Code Online (Sandbox Code Playgroud)

跟踪表明在看到最后一个术语时会发生某些事情,但没有什么好处:在一种情况下,计算停止但程序仍然挂起,而在另一种情况下,它给了我一个可爱的小崩溃转储,我要报告给甲骨文.

那么有没有办法完成我正在寻找的那种东西?

(注意:我现在正在假设串行流.但是我认为这是一种可以从并行性中受益的问题,一旦我弄清楚它是如何工作的.)

Hol*_*ger 4

如果终止条件和 \xe2\x80\x99s 结果之间存在这样的依赖关系Collector,则使用可变状态是不可避免的。但只要您不需要\xe2\x80\x99t 并行执行,实现就可以很简单:

\n\n
class MutableDouble {\n    double sum;\n}\nMutableDouble sumHolder=new MutableDouble();\nDoubleStream ds=IntStream.iterate(1, (i -> i + 1))\n                         .mapToDouble(n -> 1 / ((double)n * n));\nds.peek(term -> sumHolder.sum+=term)\n  .filter(term -> Math.abs(term)<1e-12*Math.abs(sumHolder.sum))\n  .findFirst();\nSystem.out.println(sumHolder.sum);\n
Run Code Online (Sandbox Code Playgroud)\n\n

由于它会在比较之前进行总结,因此与您原来的逻辑并不完全相同,而是类似于:

\n\n
double sum = 0;\nfor (double n = 1; ; n++) {\n    double term = 1 / (n * n);\n    sum += term;\n    if (Math.abs(term) <= 1e-12 * Math.abs(sum)) {\n        break;\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

如果你坚持原来的逻辑,它必须稍微复杂一些:

\n\n
class MutableDouble {\n    double sum, next;\n    void add(double d) {\n        sum=next;\n        next+=d;\n    }\n}\nMutableDouble sumHolder=new MutableDouble();\nDoubleStream ds=IntStream.iterate(1, (i -> i + 1))\n                         .mapToDouble(n -> 1 / ((double)n * n));\nds.peek(sumHolder::add)\n  .filter(term -> Math.abs(term)<1e-12*Math.abs(sumHolder.sum))\n  .findFirst();\n
Run Code Online (Sandbox Code Playgroud)\n