流查找方法 - 获取内存不足

时间:2014-04-07 00:45:25

标签: scala

有人可以帮助我解决我在这里失踪的问题:

def isDivisibleByRange(n: Int, r: Range) = {
    r.forall(n % _ == 0)
}  

def from(n: Int): Stream[Int] = n #:: from(n + 1)

现在,以下内容给了我一个OOM:

val o = from(1).find(isDivisibleByRange(_, Range(2, 21)))

2 个答案:

答案 0 :(得分:2)

您的代码没有任何问题,问题在于Stream.find,或者更确切地说是find中继承该方法的LinearSeqOptimized

override /*IterableLike*/
def find(p: A => Boolean): Option[A] = {
  var these = this
  while (!these.isEmpty) {
    if (p(these.head)) return Some(these.head)
    these = these.tail
  }
  None
}

此方法已编写为使用while循环运行,而不是使用递归。对于非延迟序列,这不会使用任何额外的内存,并且比递归解决方案运行得更快。不幸的是,Stream是懒惰的,当此方法与大(特别是无限)序列一起使用时,会导致内存消耗失控。发生这种情况是因为该方法始终将其this指针保留在堆栈上,因此垃圾收集器永远不会收集Stream的开头或任何其余内容。

可以通过编写递归工作的find来解决问题:

import annotation.tailrec

@tailrec
def betterFind[A](s: Stream[A], p: A => Boolean): Option[A] = {
  if (s.isEmpty)
    None
  else if (p(s.head))
    Some(s.head)
  else
    betterFind(s.tail, p)
}

在实践中,使用Stream.iterator.find而不是编写自己的方法可能更简单。 Stream.iterator会在Iterator的元素上返回Stream,即使在无限流中也可以安全使用。

答案 1 :(得分:1)

让我们稍微介绍一下你的代码:

from(1) // 2, 3, 4, 5, 6 ...
isDivisibleByRange(1, Range(2, 21)
Range(2, 21).forall(1 % _ == 0) // false
isDivisibleByRange(2, Range(2, 21)
Range(2, 21).forall(2 % _ == 0) // false
isDivisibleByRange(3, Range(2, 21)
Range(2, 21).forall(3 % _ == 0) // false
... OOME

满足(2 to 21) mod == 0的第一个数字是232792560。所以,在到达232792560之前,你会得到一个OOME。

由于stream只是一个惰性列表,因此您基本上创建了一个包含所有可能正整数的列表,这将占用您的所有内存。也许增加你的堆空间?请记住,流容器周围有一些额外的分配,而不是int的4个字节,所以也许-Xmx4G

<强>更新

使用迭代器方法,您可以在最短内存的情况下以适当的时间执行此操作(find上的Range通过Iterator实现):

Range(1, Int.MaxValue).find(r => Range(2, 21).forall(r1 => r % r1 == 0))
//Some(232792560)