我想要一种方便的方法来生成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
,但map
,filter
和朋友都不能提供所有便利。
有没有方便的库方法来制作我想要的东西?
如果没有,
有人可以建议使用“口语”Scala代码吗?
总结一下,给定一个初始对象a: A
和一个函数f: A => A
,我想要TraversableLike
(例如,可能是Iterable
)生成{{ 1}},并使用O(1)内存,a, f(a), f(f(a)), ...
,map
等函数,这些函数也返回内存中的O(1)。
答案 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
。