我有以下简单的代码
def fib(i:Long,j:Long):Stream[Long] = i #:: fib(j, i+j)
(0l /: fib(1,1).take(10000000)) (_+_)
它会抛出OutOfMemmoryError异常。 我无法理解为什么,因为我认为所有部分都使用常量内存,即惰性评估流和foldLeft ......
这些代码也不起作用
fib(1,1).take(10000000).sum or max, min e.t.c.
如何正确实现无限流并对其进行迭代操作?
Scala版本:2.9.0
另外scala javadoc说,foldLeft操作对于流是记忆安全的
/** Stream specialization of foldLeft which allows GC to collect
* along the way.
*/
@tailrec
override final def foldLeft[B](z: B)(op: (B, A) => B): B = {
if (this.isEmpty) z
else tail.foldLeft(op(z, head))(op)
}
编辑:
使用迭代器的实现仍然无用,因为它会抛出$ {domainName}异常
def fib(i:Long,j:Long): Iterator[Long] = Iterator(i) ++ fib(j, i + j)
如何在Scala中正确定义无限流/迭代器?
EDIT2: 我不关心int溢出,我只是想了解如何在scala中创建无限流/迭代器等而没有副作用。
答案 0 :(得分:6)
使用Stream
代替Iterator
的原因是您无需再次计算系列中的所有小字词。但这意味着您需要存储一千万个流节点。不幸的是,它们非常大,因此可能足以溢出默认内存。解决这个问题的唯一现实方法是从更多内存开始(例如scala -J-Xmx2G
)。 (另外,请注意,你将大幅度地溢出Long
;斐波纳契系列增长得非常快。)
P.S。我想到的迭代器实现完全不同;你不是用连接的单例Iterator
构建的:
def fib(i: Long, j: Long) = Iterator.iterate((i,j)){ case (a,b) => (b,a+b) }.map(_._1)
现在折叠时,可以放弃过去的结果。
答案 1 :(得分:3)
OutOfMemoryError独立于您使用Stream的事实发生。正如上面提到的Rex Kerr,Stream - 与Iterator不同 - 将所有内容存储在内存中。与List的不同之处在于,Stream的元素是懒惰计算的,但是一旦达到10000000,就会有10000000个元素,就像List一样。
尝试new Array[Int](10000000)
,你会遇到同样的问题。
要计算上面的斐波那契数,您可能需要使用不同的方法。您可以考虑到这样一个事实,即您只需要两个数字,而不是到目前为止发现的整个斐波那契数字。
例如:
scala> def fib(i:Long,j:Long): Iterator[Long] = Iterator(i) ++ fib(j, i + j)
fib: (i: Long,j: Long)Iterator[Long]
例如,第一个斐波那契数的索引超过1000000:
scala> fib(1, 1).indexWhere(_ > 1000000)
res12: Int = 30
编辑:我添加了以下几行来应对StackOverflow
如果你真的想使用第一百万个fibonacci数,那么上面的迭代器定义对StackOverflowError也不起作用。以下是我目前最好的想法:
class FibIterator extends Iterator[BigDecimal] {
var i: BigDecimal = 1
var j: BigDecimal = 1
def next = {val temp = i
i = i + j
j = temp
j }
def hasNext = true
}
scala> new FibIterator().take(1000000).foldLeft(0:BigDecimal)(_ + _)
res49: BigDecimal = 82742358764415552005488531917024390424162251704439978804028473661823057748584031
0652444660067860068576582339667553466723534958196114093963106431270812950808725232290398073106383520
9370070837993419439389400053162345760603732435980206131237515815087375786729469542122086546698588361
1918333940290120089979292470743729680266332315132001038214604422938050077278662240891771323175496710
6543809955073045938575199742538064756142664237279428808177636434609546136862690895665103636058513818
5599492335097606599062280930533577747023889877591518250849190138449610994983754112730003192861138966
1418736269315695488126272680440194742866966916767696600932919528743675517065891097024715258730309025
7920682881137637647091134870921415447854373518256370737719553266719856028732647721347048627996967...
答案 2 :(得分:3)
@ yura的问题:
def fib(i:Long,j:Long):Stream[Long] = i #:: fib(j, i+j)
(0l /: fib(1,1).take(10000000)) (_+_)
除了使用不能容纳10,000,000的Fibonacci的Long之外,它确实有用。也就是说,如果foldLeft写为:
fib(1,1).take(10000000).foldLeft(0L)(_+_)
查看Streams.scala来源,foldLeft()显然是为垃圾收集而设计的,但/:
未定义。
其他答案提到另一个问题。 1000万的Fibonacci是一个很大的数字,如果使用BigInt,而不是像Long一样溢出,那么一次又一次地添加绝对庞大的数字。
由于Stream.foldLeft
针对GC进行了优化,因此它看起来像是解决非常大的Fibonacci数的方法,而不是使用zip或尾递归。
// Fibonacci using BigInt
def fib(i:BigInt,j:BigInt):Stream[BigInt] = i #:: fib(j, i+j)
fib(1,0).take(10000000).foldLeft(BigInt("0"))(_+_)
上述代码的结果:10,000,000是一个8位数字。 fib中有多少个数字(10000000)? 2,089,877
答案 3 :(得分:1)
fib(1,1).take(10000000)
是方法this
的“/:
”,只要方法运行,JVM就可能会将引用视为有效,即使在这种情况下,它可能会摆脱它。
因此,您始终在流的头部保留一个引用,因此在构建到10M元素时将整个流引用。
答案 4 :(得分:1)
你可以使用递归,这很简单:
def fibSum(terms: Int, i: Long = 1, j: Long = 1, total: Long = 2): Long = {
if (terms == 2) total
else fibSum(terms - 1, j, i + j, total + i + j)
}
有了这个,你可以在几秒内“折叠”十亿个元素,但正如雷克斯指出的那样,对Fibbonaci序列的求和非常快。
如果您真的想知道原始问题的答案,并且不介意牺牲一些准确性,那么您可以这样做:
def fibSum(terms: Int, i: Double = 1, j: Double = 1, tot: Double = 2,
exp: Int = 0): String = {
if (terms == 2) "%.6f".format(tot) + " E+" + exp
else {
val (i1, j1, tot1, exp1) =
if (tot + i + j > 10) (i/10, j/10, tot/10, exp + 1)
else (i, j, tot, exp)
fibSum(terms - 1, j1, i1 + j1, tot1 + i1 + j1, exp1)
}
}
scala> fibSum(10000000)
res54: String = 2.957945 E+2089876