【问题标题】:Using Streams for iteration in Scala在 Scala 中使用 Streams 进行迭代
【发布时间】:2012-01-23 21:38:41
【问题描述】:

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

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

【问题讨论】:

    标签: scala stream


    【解决方案1】:

    这是产生 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,这样内存就可以被垃圾回收了。
    【解决方案2】:

    当您执行一系列递归计算并且单个结果取决于先前的结果(例如计算 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 来限制内存使用?
    • 丹,这是完全错误的。您的解决方案是 O(n^2),因为在每次迭代中走下 fibs 到达 fibs(n) 是 O(n)。还有,递归解是O(2^n),不是O(n^2),当然更糟。
    • @Douglas,你对运行时间是正确的,我编辑了我的答案并搞砸了符号(我最初包括使用尾递归的“最佳”解决方案,即 O(n)) ,但我认为这不会使我的回答“完全错误”。流解决方案仍然比递归解决方案快一个数量级。
    猜你喜欢
    • 1970-01-01
    • 2011-10-13
    • 2018-07-01
    • 2010-12-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多