Remember the last value received from JDK 8 lambda

I put together a brief Java implementation to extend the Taylor series for an exponential function because it was easy and fun:

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 am ashamed to admit that my employer is still using JDK 6 and JDK 7; I have not written JDK 8 during the day. I have not tried all the new features in the JDK, including lambdas.

I warmed up by writing a Taylor series extension for pi using lambda. It is light and elegant. Surprisingly, it takes a million terms to come close to six digits, but that's the nature of the series .

I decided to try and implement an exponential function using lambda. I do not want to do the naive thing and use the functions Math.pow or factorial ; the implementation that I posted without lambdas does and is beautiful.

I don’t see how every step in lambda remembers what the value of the previous term was. Can someone help a beginner lambda and give an example?

+5
source share
5 answers

A possible solution is to implement a state function to return the next member 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 using the iterate(seed, f) method, where the seed is 1 and the function that returns the next value simply increases the current iteration number i and multiplies the previous value by x / i . The flow is limited to element n with limit , and the sum is extracted using sum() .

Example call code:

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

with a very close result to real .

+4
source

Added a minor improvement to Tunaki's solution: replaced the anonymous DoubleUnaryOperator class with lambda and the attribute I with an AtomicInteger instance:

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

There is a solution that does not require stateful functions. All you need is a pair of two values, so you can express a function that maps from a pair of two values ​​to another pair. Due to the lack of a general-purpose pair type in Java, you have two options:

  • Use an array of length two:

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

    it's short but cleaner:

  • Create a highlighted type containing 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 consider the class to be simpler and more understandable in the code of a stream operation and allows both variables to be of the corresponding type.

These solutions are thread safe, in contrast to solutions containing functions with the preservation of operability, however, it is very unlikely that a parallel stream can be used here because of the dependence of each element on its previous one. Since your question is about pleasure and education, let's demonstrate how we can do this easily in parallel if we accept the storage requirement 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 low-level functions, multiplication and addition instead of pow or factorial, as you wanted, but uses the fact that the necessary operations can be decomposed into operations that do not depend on all previous elements, so that parallel processing is possible. Of course, you need large enough n to take advantage of parallel processing, and then you need the appropriate RAM to store temporary results.

+3
source

It may be too late, but here's another approach that improves Tunaki and Adam Siemion a bit, answers:

 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 an index or lambda that refers to AtomicInteger , it would be better to build a primitive int iterator from a closed-range stream.

+2
source

I want to thank all those who answered here. I included your suggestions in a solution that still had some amazing subtleties. Both answers were important.

Here's the code, including lambda implementations for sine and cosine. Getting the difference in the AtomicInteger index for each of them was key:

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

Source: https://habr.com/ru/post/1244532/


All Articles