Scala中流的合成操作

  • 本文关键字:操作 Scala scala stream
  • 更新时间 :
  • 英文 :


假设您有一个程序,它以某种方式操纵流Stream[Foo]以产生感兴趣的计算,例如

myFooStream.map(toBar).groupBy(identity).mapValues(_.size)

很好,除了现在你必须在myFooStream上做一些其他类型的计算,比如

myFooStream.map(toBar).sum

您希望以某种方式组合这些计算,这样您就不需要在流上迭代两次(假设由于某种原因,在流上进行迭代是昂贵的)。

有没有某种Scala风格的方法来处理这个问题?更抽象地说,我的问题是,我想以某种方式从这些流的迭代中抽象出这些流的计算。也就是说,最好的是,如果我能以某种方式编写两个方法f: Stream[Foo] => Barg: Stream[Foo] => Baz,并以某种方式组合fg,使它们在流的单个迭代中操作。

是否存在允许这样做的抽象?

更新的问题:我做了一些挖掘。scalaz箭头对这个问题有帮助吗?

Streams自然会尽量避免通过存储结果来多次生成元素。来自文档:

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:

一个人必须小心记忆;如果你不小心的话,你很快就会消耗掉大量的记忆。其原因是CCD_ 16的存储创建了与CCD_ 17非常相似的结构。

当然,如果项的生成不是昂贵的,而是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,使一些更常见的操作(如foldLeftreduceLeft和其他操作)以这种格式可用:

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])(_ + _, _ :+ _)

流不会迭代两次:

Stream.continually{println("bob"); 1}.take(4).map(v => v).sum
bob
bob
bob
bob
4

val bobs = Stream.continually{println("bob"); 1}.take(4)
val alices = Stream.continually{println("alice"); 2}.take(4)
bobs.zip(alices).map{ case (b, a) => a + b}.sum
bob
bob
bob
bob
alice
alice
alice
alice
12

相关内容

  • 没有找到相关文章

最新更新