Java 还记得从JDK 8 lambda返回的最后一个值吗
我为指数函数提供了一个泰勒级数展开的快速Java实现,因为它既简单又有趣:Java 还记得从JDK 8 lambda返回的最后一个值吗,java,lambda,java-8,Java,Lambda,Java 8,我为指数函数提供了一个泰勒级数展开的快速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.w
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();
}
}
package math.series;
导入java.util.stream.IntStream;
/**
*迈克尔创作的
*创建日期:2016年3月6日。
*@linkhttps://stackoverflow.com/questions/35826081/calculating-ex-in-c-sharp
*@linkhttps://en.wikipedia.org/wiki/Leibniz_formula_for_%CF%80
*/
公共级泰勒酒庄{
公共静态final int DEFAULT_NUM_TERMS=10;
公共静态void main(字符串[]args){
int n=(args.length>0)?Integer.parseInt(args[0]):默认的\u NUM\u术语;
System.out.println(“pi”);
System.out.println(String.format(“%10s%10s%10s%10s%10s”,“n”,“series”,“expected”,“error”));
双预期=Math.PI;
双系列=TaylorSeries.pi(0.0,n);
双错误=预期-系列;
System.out.println(String.format(“%10d%10.6f%10.6f%10.6f”,n,系列,应为,错误));
系统输出打印项次(“exp”);
System.out.println(String.format(“%10s%10s%10s%10s%10s”,“x”,“series”,“expected”,“error”));
对于(double x=0.0;x而言,一种可能的解决方案是实现一个有状态函数,用于返回序列中的下一项:
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();
}
这将借助以下方法创建一个DoubleStream
,其中seed为1,返回下一个值的函数只需增加当前迭代次数i
,并将前一个值乘以x/i
。流限制为n
元素,并使用
调用代码示例:
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();
}
我要感谢所有在这里回答的人。我已经将你们的建议纳入了一个仍然有一些令人惊讶的微妙之处的解决方案中。两个答案都很重要
以下是代码,包括正弦和余弦的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();
}
}
package math.series;
导入java.util.concurrent.AtomicInteger;
导入java.util.stream.DoubleStream;
导入java.util.stream.IntStream;
/**
*迈克尔创作的
*创建日期:2016年3月6日。
*@linkhttps://stackoverflow.com/questions/35826081/calculating-ex-in-c-sharp
*@linkhttps://en.wikipedia.org/wiki/Leibniz_formula_for_%CF%80
*/
公共级泰勒酒庄{
公共静态final int DEFAULT_NUM_TERMS=10;
公共静态void main(字符串[]args){
int n=10000000;
双y=1.0;
System.out.println(String.format(“使用%d术语的pi”,n));
System.out.println(String.format(“%20s%20s%20s%20s%20s”,“n”,“series”,“expected”,“error”));
双预期=Math.PI;
双系列=TaylorSeries.pi(0.0,n);
双错误=预期-系列;
System.out.println(String.format(“%20.16f%20.16f%20.16f%20.6e”,y,系列,应为,错误));
n=50;
System.out.println(String.format(“exp使用%d术语”,n));
System.out.println(String.format(“%20s%20s%20s%20s%20s”,“x”,“series”,“expected”,“error”));
对于(双x=0.0;x((i.get()&1)==0?1:-1)*((i.get()==0)?1.0/i.incrementAndGet():term*x*x/i.getAndIncrement()/i.getAndIncrement())
).限制(n).总和();
}
}
也许为时已晚,但这里有另一种方法稍微改进了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
的基本迭代器。有一种解决方案不需要有状态函数。您只需要一对两个值,就可以将ss一种从一对两个值映射到另一对值的函数。由于Java中没有通用对类型,因此有两种选择:
使用长度为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]));
这很短,但更干净的是:
创建一个专用类型,将两个值作为实例变量保存:
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
或阶乘,但它利用了这样一个事实,即所需的操作可以分解为不依赖于所有先前元素的操作,因此并行处理是可能的要从并行处理中获益,您需要适当的RAM来保存临时结果。听起来您想做点什么(我不是Java8专家,所以现在不打算尝试回答;))谢谢,@Oliver Charlesworth。我会研究一下。我正在仔细研究我的“Java8正在运行”副本试着找出它。我很欣赏这条线索。作为旁注,在pi
中,double x
在哪里使用?它不用于pi,但我在考虑exp、sin、cos等的公共接口。所有这些都有泰勒级数展开式。pi的级数可以表示为atan级数展开式;在这种情况下,x=1.0。In gnuplot语法,使用