在Scala中使用Streams进行迭代

时间:2011-12-19 19:53:45

标签: scala stream

SICP说迭代过程(例如平方根计算的牛顿法,“pi”计算等)可以用Streams表示。

是否有人在Scala中使用streams来模拟迭代?

2 个答案:

答案 0 :(得分:17)

这是生成pi近似值的一种方法:

val naturals = Stream.from(0) // 0, 1, 2, ...
val odds = naturals.map(_ * 2 + 1) // 1, 3, 5, ...
val oddInverses = odds.map(1.0d / _) // 1/1, 1/3, 1/5, ...
val alternations = Stream.iterate(1)(-_) // 1, -1, 1, ...
val products = (oddInverses zip alternations)
      .map(ia => ia._1 * ia._2) // 1/1, -1/3, 1/5, ...

// Computes a stream representing the cumulative sum of another one
def sumUp(s : Stream[Double], acc : Double = 0.0d) : Stream[Double] =
  Stream.cons(s.head + acc, sumUp(s.tail, s.head + acc))

val pi = sumUp(products).map(_ * 4.0) // Approximations of pi.

现在,假设您想要第200次迭代:

scala> pi(200)
resN: Double = 3.1465677471829556

...或300000:

scala> pi(300000)
resN : Double = 3.14159598691202

答案 1 :(得分:7)

当您执行一系列递归计算时,Streams非常有用,并且单个结果取决于之前的结果,例如计算pi。这是一个更简单的例子,考虑用于计算fibbonacci数的经典递归算法(1,2,3,5,8,13 ......):

def fib(n: Int) : Int = n match {
  case 0 => 1
  case 1 => 2
  case _ => fib(n - 1) + fib(n - 2)
}

这段代码的一个要点是虽然非常简单,但效率极低。 fib(100)几乎撞坏了我的电脑!每个递归分支为两个调用,并且您实际上是多次计算相同的值。

Streams允许您以递归方式进行动态编程,一旦计算了值,就会在每次需要时重复使用它。使用流实现上述内容:

val naturals: Stream[Int] = Stream.cons(0, naturals.map{_ + 1})
val fibs : Stream[Int] = naturals.map{
  case 0 => 1
  case 1 => 2
  case n => fibs(n - 1) + fibs( n - 2)
}
fibs(1) //2
fibs(2) //3
fibs(3) //5
fibs(100) //1445263496

当递归解在O(2 ^ n)时间内运行时,Streams解决方案在O(n ^ 2)时间内运行。由于您只需要最后2个生成的成员,因此可以使用Stream.drop轻松优化此成员,以便流大小不会溢出内存。