1我试图制作一个无限制因子函数(只是出于好奇。)这适用于大n
(尝试高达100000,它似乎工作,虽然我无法检查输出正确的价值,因为,它是大的!)
(BigInt(1) to n).reduceRight(_*_)
但我担心整个BigInt(1) to n
范围可能在记忆中,而我只需要逐个元素reduceRight
。看看Scala的标准库代码看起来BigInt(1) to n
实际上输出了整个Range
而不是懒惰的Stream
但我找到了Stream.range
我可以这样使用(通知n+1
,流范围是独占的)
Stream.range[BigInt](BigInt(1), BigInt(n+1)).reduceRight(_*_)
适用于n=10000
(出于某种原因,它需要更长的时间!这让我觉得正常范围实际上也可能是Stream
?)但n=100000
我得到这个堆栈溢出
java.lang.StackOverflowError
at java.math.BigInteger.add(Unknown Source)
at scala.math.BigInt.$plus(BigInt.scala:161)
at scala.math.Numeric$BigIntIsIntegral$class.plus(Numeric.scala:28)
at scala.math.Numeric$BigIntIsIntegral$.plus(Numeric.scala:40)
at scala.math.Numeric$BigIntIsIntegral$.plus(Numeric.scala:40)
at scala.math.Numeric$Ops.$plus(Numeric.scala:208)
at scala.collection.immutable.Stream$$anonfun$range$1.apply(Stream.scala:695)
at scala.collection.immutable.Stream$$anonfun$range$1.apply(Stream.scala:695)
at scala.collection.immutable.Stream$Cons.tail(Stream.scala:634)
at scala.collection.immutable.Stream$Cons.tail(Stream.scala:626)
at scala.collection.LinearSeqOptimized$class.reduceRight(LinearSeqOptimized.scala:130)
at scala.collection.immutable.Stream.reduceRight(Stream.scala:47)
at scala.collection.LinearSeqOptimized$class.reduceRight(LinearSeqOptimized.scala:131)
at scala.collection.immutable.Stream.reduceRight(Stream.scala:47)
at scala.collection.LinearSeqOptimized$class.reduceRight(LinearSeqOptimized.scala:131)
at scala.collection.immutable.Stream.reduceRight(Stream.scala:47)
...
很明显,reduceRight
正在调用自己这样的
reduceRight(reduceRight(first, second, op), third, op)...
因此堆栈溢出。我假设它不是尾部调用优化的,因为它首先减少然后在返回值之前运行,因此无法进行优化。那我怎么能解决这个问题呢?我是否应该抛弃功能方法并针对自定义命令式代码进行减少?
令我感到奇怪的是,(BigInt(1) to n).reduceRight(_*_)
对于大n
没有溢出,而使用流几乎相同...这里发生了什么?
答案 0 :(得分:8)
你的第一个解决方案will create a list in memory来存储反向序列是正确的。你可以简单地使用reduceLeft
(在范围上没有这个问题),但是它会以相反的方向通过范围。如果由于某种原因你想从大端开始但是保持reduceLeft
的懒惰,你可以向后创建Range
:
def fact(n: Int) = (BigInt(n) to 1 by -1).reduceLeft(_ * _)
您可以other ways轻松优化此功能。
答案 1 :(得分:7)
reduceLeft
来计算流上的内容(并按顺序调用)。您可以按如下方式进行验证:
Stream.range(1,10).map(i => print(i)).reduceLeft((a,b) => println("r"))
或者您可以使用尾递归:
final def fac(n: BigInt, prod: BigInt = BigInt(1)): BigInt = {
if (n<2) prod else fac(n-1,prod*n)
}
(正如Travis指出的那样,首先将小数字相乘会更快,这需要额外的一行)。
答案 2 :(得分:4)
请尝试使用reduceLeft
。 reduceRight
从流的末尾开始,因此强制评估每个元素 - 正是您想要避免的。
答案 3 :(得分:1)
def fac (n: BigInt=1, m:BigInt=1): Stream[BigInt] =
Stream.cons (n, fac (n * m, m+1))
fac().take (10).toList
res27: List[BigInt] = List(1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880)
也适用于take(10000).toList
。