【发布时间】:2012-01-23 21:38:41
【问题描述】:
SICP表示可以用Streams表示迭代过程(例如牛顿法的平方根计算,“pi”计算等)。
有人在 Scala 中使用 streams 来模拟迭代吗?
【问题讨论】:
SICP表示可以用Streams表示迭代过程(例如牛顿法的平方根计算,“pi”计算等)。
有人在 Scala 中使用 streams 来模拟迭代吗?
【问题讨论】:
这是产生 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
【讨论】:
def,这样内存就可以被垃圾回收了。
当您执行一系列递归计算并且单个结果取决于先前的结果(例如计算 pi)时,流非常有用。这是一个更简单的例子,考虑计算斐波那契数(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 轻松优化它,这样流大小就不会溢出内存。
【讨论】:
Stream不是随机访问。所以像fibs(n - 1) 这样的调用看起来效率很低。
drop 来限制内存使用?