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

在Scala中,从初始对象和生成下一个对象的函数创建O(1)-内存Iterable,scala,iterator,scala-2.8,iterable,Scala,Iterator,Scala 2.8,Iterable,我想要一种方便的方法来生成一个Iterable,给定一个初始对象和一个从当前对象生成下一个对象的函数,该函数会消耗O(1)内存(即,它不会缓存旧结果;如果要再次迭代,则必须再次应用该函数) 这似乎没有图书馆的支持。在Scala2.8中,方法Scala.collection.Iterable.iterate具有签名 def iterate [A] (start: A, len: Int)(f: (A) ⇒ A) : Iterable[A] def iterate [T] (start: T)(f

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

这似乎没有图书馆的支持。在Scala2.8中,方法
Scala.collection.Iterable.iterate
具有签名

def iterate [A] (start: A, len: Int)(f: (A) ⇒ A) : Iterable[A]
def iterate [T] (start: T)(f: (T) ⇒ T) : Iterator[T]
因此,它要求您提前指定您感兴趣的迭代函数应用程序的数量,我对文档的理解是,
Iterable.iterate
实际上会立即计算所有这些值。另一方面,方法
scala.collection.Iterator.iterate
具有签名

def iterate [A] (start: A, len: Int)(f: (A) ⇒ A) : Iterable[A]
def iterate [T] (start: T)(f: (T) ⇒ T) : Iterator[T]
这看起来很棒,但我们只得到了一个
迭代器
,它没有提供
映射
过滤器
和朋友的所有便利

是否有一种方便的库方法来生成我想要的内容?

如果没有

有人能推荐使用“口语化”Scala代码来实现这一点吗?


总之,给定一个初始对象
a:a
,和一个函数
f:a=>a
,我想要一个
TraversableLike
(例如,可能是一个
Iterable
),它生成
a,f(a)),…
,并使用O(1)内存,带有
map
filter
等函数,这些函数也返回O(1) 在内存中。

Stream
将执行您想要的操作,只是不保留单元格;只对值进行迭代

val it = new Iterable[Int] {
  def iterator = Iterator.iterate(0)(_+1)
  override
  def toString: String = "Infinite iterable"
}
令人遗憾的是,人们普遍存在一种误解,即流本质上会缓存它们计算的每个值

如果你这样写:

val s1: Stream[Thing] = initialValue #:: «expression computing next value»
实际上,流产生的每个值都会被保留,但这不是必需的。如果您写入:

def s2: Stream[Thing] = initialValue #:: «expression computing next value»
如果调用者只是迭代流的值,但不记得流的值本身(特别是它的任何cons单元格),则不会发生不必要的保留。当然,在这个公式中,每次调用都会从固定的初始值开始创建一个新的
。这不是必需的:

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

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

迭代器。使用过滤器迭代
演示:

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中可能不起作用,因为打印步骤可能会干扰内存管理)

JAVA_OPTS=“-Xmx128m”scala-cp classes I
将显示过滤工作正常并且是惰性的。如果不是在恒定内存中进行过滤,则会导致堆错误(因为它分配的是900*10mb)


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

迭代器正是您所需要的。迭代器确实有map、filter、takeWhile和许多其他方法,它们在内存中是O(1)。我认为内存中没有另一种具有O(1)的收集类型

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

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

作为“线索”:再读一些API,我开始怀疑一个好的答案会提到
TraversableViewLike
,但我也越来越困惑。迭代器有map、filter和friends…你确定它们使用的不仅仅是常量内存吗?没错,在
迭代器上可以找到map和filter等,不要做傻事比如强制使用
迭代器
。但是
Iterable
会更方便;为什么我不应该期望能够使用
tail
(每当调用
迭代器
,它应该通过调用
next
来删除第一个元素,然后再返回
迭代器
),等等?(事实上,当我试图将我的代码从预期的
Iterable
s切换到
Iterator
s时,这是我必须解决的问题。)我不明白--我需要能够将这个对象传递给其他使用者;也就是说,未知的其他代码实际上会进行迭代。我不知道如果不将引用传递给
流的头部
,我怎么能做到这一点。这是一个限制。正如我所说的,您必须构造代码来传递
尾部调用优化链。但是“未知”代码知道它正在获得一个
,因此它知道它不能保留对其(流-)cons单元格的引用。不,这真的不行。为什么“未知”代码会代码知道什么吗?如果其他人调用我的代码,为什么他们不把返回值当作一个
Iterable
不合适?否:请看我对他的回答的评论。也许我没有足够清楚地解释我想要什么,应该用一个新问题再试一次。感谢你提供的细节让我相信一切都是O(1)。我会试试这个。这会在主干中打印“无限可编辑”。至少据我所知,
it-map{uuu+1}take 5
不会终止,但是,因为
map
会试图强制
Iterable
@Scott
Iterable
不一定是懒惰的。除非你花时间让所有方法都变得懒惰,否则这就是提供的。但是,
it.view map{1}take 5
会起作用,所以我看不出为什么要担心它。