【问题标题】:Restructuring a method to fix tail recursive call not in tail position重构一种方法来修复尾部递归调用不在尾部位置
【发布时间】:2015-03-29 18:36:31
【问题描述】:

考虑以下递归幂法乘法:

import scala.annotation.tailrec
@tailrec def mult(x: Double, n:Int) : Double = {
      n match {
    case 0 => 1
    case 1 => x
    case _ if ((n & 0x01) != 0) =>  x * mult(x*x, (n-1)/2)
    case _ =>  mult(x*x, n/2)
    }
}

编译错误为:

<console>:28: error: could not optimize @tailrec annotated method mult: 
it contains a recursive call not in tail position
             y *  mult(x*x,(n-2)/2)
               ^

所以 .. 鉴于递归调用 最后一个条目 - 我认为产品 y *(尾递归子句)存在问题?如何正确构建它?

更新

这是已接受答案的修改版本 - 我很懒,只是在被调用的方法中放置了第三个累加器。

@tailrec def mult(x: Double, n:Int, accum: Double = 1.0) : Double = {
        n match {
      case 0 => accum
      case 1 => accum * x
      case _ if ((n & 0x01) != 0) =>  mult(x*x, (n-1)/2, x * accum)
      case _ =>  mult(x*x, n/2, accum)
      }
  }

mult: (x: Double, n: Int, accum: Double)Double

试试看:

scala> mult(2, 7)
res0: Double = 128.0

scala> mult(2, 8)
res1: Double = 256.0

【问题讨论】:

    标签: scala tail-recursion


    【解决方案1】:

    有两种方法可以解决此类问题。第一个是在调用中移动乘法,可能通过添加一个辅助方法:

    import scala.annotation.tailrec
    
    def mult(x: Double, n: Int): Double = {
      @tailrec
      def go(x: Double, n: Int, mult: Double): Double = n match {
        case 0 => mult
        case 1 => mult * x
        case _ if (n & 0x01) != 0 => go(x * x, (n - 1) / 2, x * mult)
        case _ => go(x * x, n / 2, mult)
      }
      go(x, n, 1)
    }
    

    另一种方法实际上并不能回答您的问题,但在某些情况下它可能是一种更方便的方法。这叫“蹦床”:

    import scala.util.control.TailCalls._
    
    def mult(x: Double, n: Int): Double = {
      def go(x: Double, n: Int): TailRec[Double] = n match {
        case 0 => done(1)
        case 1 => done(x)
        case _ if (n & 0x01) != 0 => tailcall(go(x * x, (n - 1) / 2).map(_ * x))
        case _ => tailcall(go(x * x, n / 2))
      }
      go(x, n).result
    }
    

    这不需要你重构你的方法,并且保证不会爆栈,但它确实会引入一些额外的开销。

    【讨论】:

    • 感谢蹦床的解释。顺便说一句,我的 OP 中有一个错误,您(不幸的是信任我!)并复制了它。我会在这里更新。
    • 修复了 OP 中的错误。请从我的(不正确的)单个调用中创建两个递归调用 - 你可以看到更新。
    【解决方案2】:

    尾递归调用是那些最后一条语句仅是函数调用本身的调用。 即您的代码的最后一条语句应该是 mult(x*x,(n-2)/2) only。

    你可以试试这个。

    import scala.annotation.tailrec
      @tailrec
      def mult(x: Double, n:Int,res:Double=1) : Double = {
        n match {
          case 0 => res
          case _ => mult(x,n-1,res *x)
        }
      }
    

    【讨论】:

    • 我明白你关于使用蓄电池的观点。但是,您的实际实现不会使功率加倍;)
    • 鉴于您的回答在某种意义上回答了“为什么”错误正在发生 - 但没有提供可比较的代码答案,我会投票但暂时不奖励。
    • 添加一个参数会使 API 变得混乱,即使你给它一个默认值。在许多情况下,我的回答中的方法更可取。
    • 我是 scala 的新手,还在学习它。感谢特拉维斯的建议。会记住的:)
    【解决方案3】:

    你的函数mult 不是尾递归的,因为在函数体中你想对递归调用的结果做一些事情,即你想将它与y 相乘。

    要使这个尾递归,您应该构造函数mult,以便它可以将值y 作为参数以在递归调用后删除乘法。这是一个简单的阶乘示例:http://c2.com/cgi/wiki?TailRecursion

    【讨论】:

      猜你喜欢
      • 2015-08-11
      • 2015-06-02
      • 1970-01-01
      • 2016-06-23
      • 1970-01-01
      • 2016-08-25
      • 1970-01-01
      • 2016-03-21
      • 2017-04-10
      相关资源
      最近更新 更多