簡體   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