Streams 通过记忆结果自然会尽量避免多次生成元素。来自docs:
Stream 类还使用了记忆功能,以便将先前计算的值从 Stream 元素转换为 A 类型的具体值。
我们可以看到,通过构造一个Stream,它会在每次生成元素时打印,并运行多个操作:
val stream = Stream.from(0).map(x => { println(x); x }).take(10) //prints 0
val double = stream.map(_ * 2).take(5).toList //prints 1 through 4
val sum = stream.sum //prints 5 through 9
val sum2 = stream.sum //doesn't print any more
只要您使用val 而不是def,这将有效:
只要有东西抓住头,头就会抓住尾巴,所以它会递归地继续。另一方面,如果头部没有任何东西(例如,我们使用def 来定义Stream),那么一旦不再直接使用它,它就会消失。
这个备忘录意味着你必须小心Streams:
一个人必须小心记忆;如果你不小心,你会很快吃掉大量的内存。原因是Stream 的记忆创建了一个很像scala.collection.immutable.List 的结构。
当然,如果项目的生成不是什么昂贵的,但Stream的实际遍历,或者因为太昂贵而无法使用记忆,可以随时使用foldLeft一个元组,跟踪多个值:
//Only prints 0-9 once, even if stream is a def
val (sum, double) = stream.foldLeft(0 -> List.empty[Int]) {
case ((sum, list), next) => (sum + next, list :+ (next * 2))
}
如果这是一个足够常见的操作,您甚至可以丰富 Stream 以使一些更常见的操作(例如 foldLeft、reduceLeft 和其他一些以这种格式可用:
implicit class RichStream[T](val stream: Stream[T]) extends AnyVal {
def doubleFoldLeft[A, B](start1: A, start2: B)(f: (A, T) => A, g: (B, T) => B) = stream.foldLeft(start1 -> start2) {
case ((aAcc, bAcc), next) => (f(aAcc, next), g(bAcc, next))
}
}
这将允许您执行以下操作:
val (sum, double) = stream.doubleFoldLeft(0, List.empty[Int])(_ + _, _ :+ _)