繁体   English   中英

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

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

我为指数函数汇总了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();
    }
}

我很惭愧承认我的雇主仍在使用JDK 6和JDK 7; 我在工作期间没有写JDK 8。 我还没有弄清楚JDK中的所有新功能,包括lambdas。

我通过使用lambda编写了一个针对pi的泰勒系列扩展来热身。 它简单而优雅。 令人惊讶的是,它需要一百万个术语才能收敛到六位精度,但这就是该系列本质

我决定尝试使用lambda实现指数函数。 我不想做天真的事情并使用Math.powfactorial函数; 我发布的没有lambdas的实现很好地完成了。

我看不出如何让lambda中的每一步都记住上一个术语的价值。 任何人都可以帮助lambda初学者并举个例子吗?

一种可能的解决方案是实现一个有状态函数,用于返回序列中的下一个术语:

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();
}

这将在iterate(seed, f)方法的帮助下创建DoubleStream ,其中种子为1,并且返回下一个值的函数简单地递增当前迭代次数i并将先前值与x / i相乘。 Stream仅限于具有limit n元素,并使用sum()检索sum()

样本调用代码:

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

结果非常接近真实的结果。

为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();
}

有一种解决方案不需要有状态功能。 您所需要的只是一对两个值,因此您可以表达一个函数,该函数从一对两个值映射到另一个值。 由于Java中缺少通用对类型,您有两种选择:

  1. 使用长度为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])); 

    这很简短,但更清洁的是:

  2. 创建一个专用类型,将两个值保存为实例变量:

     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 )); 

    如果计算类,这需要更多代码,但在流操作代码中更简单,更易读,并允许两个变量都具有适当的类型。

这些解决方案是线程安全的,与具有有状态功能的解决方案不同,但是,由于每个元素与前一个元素的相关性,它不太可能从这里受益于并行流。 既然你的问题是关于有趣和教育的话,那么让我们来说明,如果我们根据迭代次数接受存储的要求,我们如何能够轻松地同时做到这一点:

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();

这个解决方案仍然使用低级函数,乘法和加法而不是pow或factorial,但是它利用了这样一个事实,即所需的操作可以分解为不依赖于所有先前元素的操作,因此并行处理是可能。 当然,你需要一个足够大的n来从并行处理中受益,然后,你需要适当的RAM来保存临时结果。

也许为时已晚,但这是另一种方法,略微改善了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();
}

而不是保存索引状态的匿名内部类或引用AtomicInteger的lambda,最好从闭合范围流构建int的原始迭代器。

我要感谢所有回复的人。 我已经将您的建议纳入了一个仍然有一些令人惊讶的微妙之处的解决方案。 这两个答案都很重要。

这是代码,包括正弦和余弦的lambda实现。 获取每个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