在Scala中,从初始对象和生成下一个对象的函数创建O(1)-memory Iterable

时间:2010-09-24 03:44:17

标签: scala iterator scala-2.8 iterable

我想要一种方便的方法来生成Iterable,给定一个初始对象和一个从当前对象生成下一个对象的函数,它消耗O(1)内存(即,它不会缓存旧的结果;如果您想再次迭代,则必须再次应用该函数。)

似乎没有图书馆支持。在Scala 2.8中,方法scala.collection.Iterable.iterate具有签名

def iterate [A] (start: A, len: Int)(f: (A) ⇒ A) : Iterable[A]

所以它要求你提前指定你感兴趣的迭代函数应用程序的数量,而我对文档的理解是Iterable.iterate实际上会立即计算所有这些值。另一方面,方法scala.collection.Iterator.iterate具有签名

def iterate [T] (start: T)(f: (T) ⇒ T) : Iterator[T]

看起来很棒,但我们只得到Iterator,但mapfilter和朋友都不能提供所有便利。

  

有没有方便的库方法来制作我想要的东西?

如果没有,

  

有人可以建议使用“口语”Scala代码吗?

总结一下,给定一个初始对象a: A和一个函数f: A => A,我想要TraversableLike(例如,可能是Iterable)生成{{ 1}},并使用O(1)内存,a, f(a), f(f(a)), ...map等函数,这些函数也返回内存中的O(1)。

4 个答案:

答案 0 :(得分:10)

Stream会做你想做的事,只是不要抓住细胞;只迭代值。

一种令人遗憾的常见误解是,Streams固有地缓存了他们计算的每一个值。

如果你这样写:

val s1: Stream[Thing] = initialValue #:: «expression computing next value»

然后确实保留了流生成的每个值,但这不是必需的。如果你写:

def s2: Stream[Thing] = initialValue #:: «expression computing next value»

如果调用者只是遍历流的值但是不记得Stream值本身(特别是其任何cons单元格),则不会发生不需要的保留。当然,在这个公式中,每个调用都会从固定的初始值开始创建一个新的Stream。这不是必要的:

def s3(start: Thing): Stream[Thing] = start #:: «expression computing next value»

您必须注意的一件事是将Stream传递给方法。这样做将捕获方法参数中传递的流的头部。解决此问题的一种方法是使用尾递归代码处理流。

答案 1 :(得分:3)

带过滤器的

Iterator.iterate演示:

object I {
  def main(args:Array[String]) {
    val mb = 1024 * 1024
    val gen = Iterator.iterate(new Array[Int](10 * mb)){arr => 
      val res = new Array[Int](10 * mb)
      arr.copyToArray(res)
      println("allocated 10mb")
      res(0) = arr(0) + 1 // store iteration count in first elem of new array
      res
    }
    // take 1 out of 100
    val gen2 = gen filter (arr => arr(0) % 100 == 0) 
    // print first 10 filtered
    gen2.take(10).foreach { arr => println("filtered " + arr(0)) } 
  }
}

(这可能在REPL中不起作用,因为PRINT步骤可能会破坏内存管理)

JAVA_OPTS="-Xmx128m" scala -cp classes I将显示过滤有效并且是懒惰的。如果它没有在常量内存中完成会导致堆错误(因为它分配了类似900 * 10mb的东西)。

使用JAVA_OPTS="-Xmx128m -verbose:gc" scala -cp classes I查看垃圾收集事件。

答案 2 :(得分:2)

迭代器就是你想要的东西。迭代器确实有map,filter,takeWhile和许多其他方法,这些方法在内存中都是O(1)。我不认为在内存中有另外的O(1)集合类型。

答案 3 :(得分:1)

val it = new Iterable[Int] {
  def iterator = Iterator.iterate(0)(_+1)
  override
  def toString: String = "Infinite iterable"
}

不要在REPL上尝试(除非将其嵌入到对象或类中),因为REPL会尝试打印它,并且它不会使用toString