Java8 数值流应用
那么什么时勾股数呢?我们得回到从前。在一堂激动人心的数学课上,你了解到,古希腊数学家毕达哥拉斯发现了某些三元数(a, b, c)满足公式a * a + b * b = c * c,其中a, b, c都是整数。例如(3,4,5)就是一组有效的勾股数,因为 3*3 + 4*4 = 5*5或者9 + 16 = 25.这样的三元数有无限组。例如,(5, 12, 13),(6,8,10)和(7,24,25)都是有效的勾股数。勾股数很有用,因为它们描述的正好是直角三角形的三条边,如图下图所示:
2. 表示三元数
那么,怎么入手呢?第一步是定义一个三元数。虽然更恰当的做法是定义一个新的类来表示三元数,但这里你可以使用具有三个元素的int数组,比如new int[] {3,4,5},来表示勾股数(3,4,5)。现在你就可以用数组索引访问每一个元素。
3. 筛选成立的组合
假如有人为你提供了三元数中的前2个数字:a和b。怎么知道他是否能形成一组勾股数呢?你需要测试a*a + b*b 的平方根是不是整数,那就是说它没有小数部分——在java里可以使用expr % 1表示。如果他不是整数,那就说c不是整数。你可以用filter操作表达这个要求
Filter(b -> Math.sqrt(a*a + b*b)%1==0)
假设周围的代码给a提供了一个值,并且stream提供了b可能出现的值,filter将只会选出那些可以与a组成勾股数的b。你可能在想math.sqrt(a*a + b*b) == 0这一行是怎么回事。简单的说,这是一种测试Math.sqrt(a*a + b*b) 返回的结果是不是整数的方法,如果平方根的结果带了小数,如9.1,这个条件就不成立。
4. 生成三元组
在筛选之后,你知道a和b能够组成一个正确的组合。现在需要创建一个三元组。你可以使用map操作,像下面这样把每个元素转换成一个勾股数组:
Stream.filter(b -> Math.sqrt(a*a + b*b)%1==0)
.map( b -> new int[]{a, b, (int)Math.sqrt(a*a + b*b)}
5. 生成b值
胜利在望,现在你需要生成b的值。前面已经看到,stream.rangeClosed让你可以在给定区间内生成一个数值流。你可以用它来给b提供数值,这里是1到100:
IntStream.rangeClosed(1,100)
.filter(b -> Math.sqrt(a*a + b*b)%1==0)
.boxed()
.map(b->new int[]{a, b, (int)Math.sqrt(a*a + b*b)})
请注意,你在filter之后调用boxed,从rangeClosed返回的IntStream生成一个Stream
IntStream.rangeClosed(1,100)
.filter(b -> Math.sqrt(a*a + b*b)%1==0)
.mapToObj(b-> new int[]{a, b, (int)Math.sqrt(a*a + b*b)}};
6. 生成值
这里有一个关键的假设,给出了a的值。现在,只要已知a的值,你就有了一个可以生成勾股数的流。如何解决这个问题呢?就像b一样,你需要为a生成数值!最终的解决方案如下所示:
Stream
IntStream.rangeClosed(1,100)
.flatMap(a->IntStream.rangeClosed(a, 100)
.filter(b ->Math.sqrt(a*a +b*b)%1==0)
.mapToObj(b -> new int[]{a, b, (int)Math.sqrt(a*a + b*b)}));
好的,flatMap又是怎么回事呢?首先,创建一个从1到100的数值范围来生成a的值。对每个给定的a值,创建一个三元数流。要是把a的值映射到三元数流的话,就会得到一个由流构成的流。FlatMap方法在做映射的同时,还会把所有生成的三元数据流扁平化成一个流。这样你就得到了一个三元数流。还要注意,我们把b的范围改成a到100。没有必要再从1开始了,否则就会造成重复的三元数,例如(3, 4,5)和(4,3,5)。
7. 运行代码
现在你可以运行解决方案,并且可以利用我们前面看到的limit命令,明确限定从生成的流中要返回多少组勾股流了:
pythagoreanThriples.limit(5)
.forEach( t ->
System.out.println(t[0] + “ ” + t[1] + “ “ + t[2])
这样就会打印:
3,4,5
5,12,13
6,8,10
7,24,25
8,15,17
8. 你还能做的更好嘛?
目前的解决方法并不是最优的,因为你每次都要两次平方根。让代码更为紧凑的一种可能的方法是,先生成所有的三元数(a*a , b*b, c*c),然后再筛选符合条件的:
Stream
IntStream.rangeClosed(1, 100).boxed()
.flatMap( a -> IntStream.rangeClosed(a, 100)
.mapToObj( b-> new Double[]{a, b, Math.sqrt(a*a+b*b)}).filter( t-> t[2]%1==0));
参考书:java8 实战