简体   繁体   English

记住从JDK 8 lambda返回的最后一个值

[英]Remember last value returned from a JDK 8 lambda

I threw together a quick Java implementation of a Taylor series expansion for the exponential function, because it was easy and fun: 我为指数函数汇总了Taylor系列扩展的快速Java实现,因为它简单而有趣:

package math.series;

import java.util.stream.IntStream;

/**
 * Created by Michael
 * Creation date 3/6/2016.
 * @link https://stackoverflow.com/questions/35826081/calculating-ex-in-c-sharp
 * @link https://en.wikipedia.org/wiki/Leibniz_formula_for_%CF%80
 */
public class TaylorSeries {


    public static final int DEFAULT_NUM_TERMS = 10;

    public static void main(String[] args) {
        int n = (args.length > 0) ? Integer.parseInt(args[0]) : DEFAULT_NUM_TERMS;
        System.out.println("pi");
        System.out.println(String.format("%10s %10s %10s %10s", "n", "series", "expected", "error"));
        double expected = Math.PI;
        double series = TaylorSeries.pi(0.0, n);
        double error = expected - series;
        System.out.println(String.format("%10d %10.6f %10.6f %10.6f", n, series, expected, error));

        System.out.println("exp");
        System.out.println(String.format("%10s %10s %10s %10s", "x", "series", "expected", "error"));
        for (double x = 0.0; x <= 3.0; x += 0.25) {
            expected = Math.exp(x);
            series = TaylorSeries.exp(x, n);
            error = expected - series;
            System.out.println(String.format("%10.6f %10.6f %10.6f %10.6f", x, series, expected, error));
        }
    }

    public static double exp(double x, int n) {
        double sum = 1.0;
        double term = 1.0;
        for (int i = 1; i <= n; ++i) {
            term *= x / i;
            sum += term;
        }
        return sum;
    }

    public static double pi(double x, int n) {
        return IntStream.range(0, n)
                .mapToDouble(i -> 8.0/(4*i+1)/(4*i+3))
                .sum();
    }
}

I'm ashamed to admit that my employer is still using JDK 6 and JDK 7; 我很惭愧承认我的雇主仍在使用JDK 6和JDK 7; I'm not writing on JDK 8 during my work day yet. 我在工作期间没有写JDK 8。 I have not groked all new features in the JDK, including lambdas. 我还没有弄清楚JDK中的所有新功能,包括lambdas。

I warmed up by writing a Taylor series expansion for pi using lambda. 我通过使用lambda编写了一个针对pi的泰勒系列扩展来热身。 It's easy and elegant. 它简单而优雅。 Surprisingly, it requires a million terms to converge to six digits of accuracy, but that's the nature of the series . 令人惊讶的是,它需要一百万个术语才能收敛到六位精度,但这就是该系列本质

I decided to try and implement the exponential function using a lambda. 我决定尝试使用lambda实现指数函数。 I don't want to do the naive thing and use the Math.pow or factorial functions; 我不想做天真的事情并使用Math.powfactorial函数; the implementation I posted without lambdas accomplishes both nicely. 我发布的没有lambdas的实现很好地完成了。

I can't see how to have each step in the lambda remember what the value of the previous term was. 我看不出如何让lambda中的每一步都记住上一个术语的价值。 Can anyone help a lambda beginner and give an example? 任何人都可以帮助lambda初学者并举个例子吗?

A possible solution is to implement a stateful function for returning the next term in the sequence: 一种可能的解决方案是实现一个有状态函数,用于返回序列中的下一个术语:

public static double exp(double x, int n) {
    return DoubleStream.iterate(1, new DoubleUnaryOperator() {

        private int i = 1;

        @Override
        public double applyAsDouble(double operand) {
            return operand * x / i++;
        }

    }).limit(n).sum();
}

This will create a DoubleStream with the help of the iterate(seed, f) method where the seed is 1 and the function, which returns the next value, simply increments the current iteration number i and multiplies the previous value with x / i . 这将在iterate(seed, f)方法的帮助下创建DoubleStream ,其中种子为1,并且返回下一个值的函数简单地递增当前迭代次数i并将先前值与x / i相乘。 The Stream is limited to n element with limit and the sum is retrieved with sum() . Stream仅限于具有limit n元素,并使用sum()检索sum()

Sample calling code: 样本调用代码:

public static void main(String[] args) {
    System.out.println(exp(3, 500)); // prints "20.085536923187668"
}

with a result very close to the real one . 结果非常接近真实的结果。

Added a small improvement to Tunaki's solution: replaced the DoubleUnaryOperator anonymous class with a lambda and the i attribute with an AtomicInteger instance: 为Tunaki的解决方案添加了一个小改进:用lambda替换DoubleUnaryOperator匿名类,用AtomicInteger实例替换i属性:

public static double exp(double x, int n) {
    final AtomicInteger integer = new AtomicInteger(1);
    return DoubleStream.iterate(
       1.0, 
       operand -> operand * x / integer.getAndIncrement()
    ).limit(n).sum();
}

There is a solution which doesn't require stateful functions. 有一种解决方案不需要有状态功能。 All you need is a pair of two values so you can express a function which maps from a pair of two values to another pair. 您所需要的只是一对两个值,因此您可以表达一个函数,该函数从一对两个值映射到另一个值。 Due to the absence of a general purpose pair type in Java, you have two options: 由于Java中缺少通用对类型,您有两种选择:

  1. Use an array of length two: 使用长度为2的数组:

     double exp=Stream.iterate(new double[]{1, 1}, a -> new double[]{ a[0]*x/a[1], a[1]+1}) .limit(n+1).collect(Collectors.summingDouble(a -> a[0])); 

    this is short, but cleaner is: 这很简短,但更清洁的是:

  2. Create a dedicated type holding the two values as instance variables: 创建一个专用类型,将两个值保存为实例变量:

     final class Item { final double term; final int index; Item(double t, int i) { term=t; index=i; } } double exp=Stream.iterate(new Item(1, 1), i -> new Item(i.term*x/i.index, i.index+1)) .limit(n+1).collect(Collectors.summingDouble(i -> i.term )); 

    This requires more code if you count the class but is simpler and more readable in the stream operation code and allows both variables to have an appropriate type. 如果计算类,这需要更多代码,但在流操作代码中更简单,更易读,并允许两个变量都具有适当的类型。

These solutions are thread-safe, unlike the solutions bearing stateful functions, however, it's very unlikely to benefit from a parallel stream here, due to the dependency of each element to its previous one. 这些解决方案是线程安全的,与具有有状态功能的解决方案不同,但是,由于每个元素与前一个元素的相关性,它不太可能从这里受益于并行流。 Since your question is about fun and education, let's demonstrate, how we can do that easily in parallel, if we accept the requirement of storage depending on the number of iterations: 既然你的问题是关于有趣和教育的话,那么让我们来说明,如果我们根据迭代次数接受存储的要求,我们如何能够轻松地同时做到这一点:

double[] array=new double[n+1];
Arrays.parallelSetAll(array, index -> index==0? 1: x/index);
Arrays.parallelPrefix(array, (a,b) -> a*b);
// we could do the last step as prefix op as well:
//Arrays.parallelPrefix(array, Double::sum);
//double exp=array[n];
// but a straight forward summing is better:
double exp=Arrays.stream(array).parallel().sum();

This solution still uses the low level functions, multiplication and addition instead of pow or factorial, as you wished, but it utilizes the fact that the required operations can be decomposed into operations which do not depend on all previous elements, so that parallel processing is possible. 这个解决方案仍然使用低级函数,乘法和加法而不是pow或factorial,但是它利用了这样一个事实,即所需的操作可以分解为不依赖于所有先前元素的操作,因此并行处理是可能。 Of course, you'll need a sufficiently large n to benefit from parallel processing and then, you'll need appropriate RAM to hold the temporary results. 当然,你需要一个足够大的n来从并行处理中受益,然后,你需要适当的RAM来保存临时结果。

Maybe it's too late, but here's another approach that slightly improves Tunaki and Adam Siemion's answers: 也许为时已晚,但这是另一种方法,略微改善了Tunaki和Adam Siemion的答案:

public static double exp(double x, int n) {
    PrimitiveIterator.OfInt i = IntStream.rangeClosed(1, n).iterator();
    return DoubleStream.iterate(1.0, term -> term * x / i.next())
            .limit(n)
            .sum();
}

Instead of an anonymous inner class that holds state for the index or a lambda that references an AtomicInteger , it would be better to build a primitive iterator of int from a closed range stream. 而不是保存索引状态的匿名内部类或引用AtomicInteger的lambda,最好从闭合范围流构建int的原始迭代器。

I'd like to thank all those who responded here. 我要感谢所有回复的人。 I've incorporated your suggestions into a solution that still had some surprising subtleties. 我已经将您的建议纳入了一个仍然有一些令人惊讶的微妙之处的解决方案。 Both answers were important. 这两个答案都很重要。

Here's the code, including lambda implementations for both sine and cosine. 这是代码,包括正弦和余弦的lambda实现。 Getting the difference in the AtomicInteger index for each was key: 获取每个AtomicInteger索引的差异是关键:

package math.series;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.DoubleStream;
import java.util.stream.IntStream;

/**
 * Created by Michael
 * Creation date 3/6/2016.
 * @link https://stackoverflow.com/questions/35826081/calculating-ex-in-c-sharp
 * @link https://en.wikipedia.org/wiki/Leibniz_formula_for_%CF%80
 */
public class TaylorSeries {


    public static final int DEFAULT_NUM_TERMS = 10;

    public static void main(String[] args) {
        int n = 10000000;
        double y = 1.0;
        System.out.println(String.format("pi using %d terms", n));
        System.out.println(String.format("%20s %20s %20s %20s", "n", "series", "expected", "error"));
        double expected = Math.PI;
        double series = TaylorSeries.pi(0.0, n);
        double error = expected - series;
        System.out.println(String.format("%20.16f %20.16f %20.16f %20.6e", y, series, expected, error));

        n = 50;
        System.out.println(String.format("exp using %d terms", n));
        System.out.println(String.format("%20s %20s %20s %20s", "x", "series", "expected", "error"));
        for (double x = 0.0; x <= 3.0; x += 0.25) {
            expected = Math.exp(x);
            series = TaylorSeries.expLambda(x, n);
            error = expected - series;
            System.out.println(String.format("%20.16f %20.16f %20.16f %20.6e", x, series, expected, error));
        }

        System.out.println(String.format("sin using %d terms", n));
        System.out.println(String.format("%20s %20s %20s %20s", "x", "series", "expected", "error"));
        for (double x = 0.0; x <= Math.PI; x += Math.PI/20.0) {
            expected = Math.sin(x);
            series = TaylorSeries.sinLambda(x, n);
            error = expected - series;
            System.out.println(String.format("%20.16f %20.16f %20.16f %20.6e", x, series, expected, error));
        }

        System.out.println(String.format("cos using %d terms", n));
        System.out.println(String.format("%20s %20s %20s %20s", "x", "series", "expected", "error"));
        for (double x = 0.0; x <= Math.PI; x += Math.PI/20.0) {
            expected = Math.cos(x);
            series = TaylorSeries.cosLambda(x, n);
            error = expected - series;
            System.out.println(String.format("%20.16f %20.16f %20.16f %20.6e", x, series, expected, error));
        }
    }

    public static double exp(double x, int n) {
        double sum = 1.0;
        double term = 1.0;
        for (int i = 1; i <= n; ++i) {
            term *= x / i;
            sum += term;
        }
        return sum;
    }

    public static double pi(double x, int n) {
        return IntStream.range(0, n)
                .mapToDouble(i -> 8.0/(4*i+1)/(4*i+3))
                .sum();
    }

    /**
     * A JDK 8 implementation for exp
     * @param x function argument
     * @param n terms to include in the series sum
     * @return exp(x)
     * @link https://stackoverflow.com/questions/35830072/taylor-series-using-jdk-8-lambdas
     */
    public static double expLambda(double x, int n) {
        final AtomicInteger i = new AtomicInteger(1);
        return DoubleStream.iterate(
                1.0,
                term -> term*x/i.getAndIncrement()
        ).limit(n).sum();
    }

    public static double sinLambda(double x, int n) {
        final AtomicInteger i = new AtomicInteger(0);
        return DoubleStream.iterate(
            0.0,
            term -> ((i.get() & 1) == 0 ? 1 : -1)*((i.get() == 0) ? x/i.incrementAndGet() : term*x*x/i.incrementAndGet()/i.incrementAndGet())
        ).limit(n).sum();
    }


    public static double cosLambda(double x, int n) {
        final AtomicInteger i = new AtomicInteger(0);
        return DoubleStream.iterate(
                0.0,
                term -> ((i.get() & 1) == 0 ? 1 : -1)*((i.get() == 0) ? 1.0/i.incrementAndGet() : term*x*x/i.getAndIncrement()/i.getAndIncrement())
        ).limit(n).sum();
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM