如何转换ackermann函数的变体以支持尾部调用?

问题描述

我目前正在解决一个问题,该问题是在scala中实现带尾调用优化支持的ackermann函数的变体,以使堆栈不会溢出。

问题是,我找不到优化它的方法。有人告诉我通行证样式(CPS)会有所帮助,但是即使我成功地用CPS样式重新实现了它,我仍然迷失了。

ackermann函数的变化如下:

ppa(p,a,b) = p(a,b)               (if a <= 0 or b <= 0)
ppa(p,ppa(p,a-1,b))  (if p(a,b) is even and a,b > 0)
ppa(p,b) = p(ppa(p,b-1),b)  (if p(a,b) is odd and a,b > 0)

未经优化的代码如下:

def ppa(p: (Int,Int) => Int,a: Int,b: Int): Int = {
  def ppa_cont(a: Int,b: Int,ret: (Int,Int) => Int): Int = {
    if (a <= 0 || b <= 0) ret(a,b)
    else (a,b) match {
      case (_,_) if (p(a,b) % 2 == 0) => ret(a,ppa_cont(a-1,b,(x,y) => ret(x,y)))
      case (_,_) => ret(ppa_cont(a,b-1,y)),b)
    }
  }

  ppa_cont(a,p)
}

另一个审判是这样的

def ppa(p: (Int,cont: (Int,Int) => Int): (Int,Int) => Int = {
    if (a <= 0 || b <= 0) cont
    else if (p(a,b) % 2 == 0) (a,b) => cont(a,cont)(a-1,b))
    else (a,b) => cont(ppa_cont(a,cont)(a,b)
  }
 
  ppa_cont(a,p)(a,b)
}

我试图像这样尾声优化它:

def ppa(p: (Int,b: Int): Int = {
  @annotation.tailrec
  def ppa_cont(a: Int,Int) => TailRec[Int]): TailRec[Int] = {
    if (a <= 0 || b <= 0) tailcall(ret(a,b) % 2 == 0) => {
        tailcall(ret(a,y) => ret(x-1,y))))
      }
      case (_,_) => {
        tailcall(ret(ppa_cont(a,y-1)),b))
      }
    }
  }

  val lifted: (Int,Int) => TailRec[Int] = (x,y) => done(p(x,y))

  ppa_cont(a,lifted).result
}

但是由于类型不匹配而无法编译。

可能是什么问题?我走错了方向吗?小提示和帮助之手将不胜感激。 Thx:)

p.s。我从why scala doesn't make tail call optimization?

得到了提示

解决方法

尝试cats.free.Trampolinescala.util.control.TailCalls.TailRec。不是@tailrec,而是堆栈安全的。

import scala.util.control.TailCalls._

def ppa(p: (Int,Int) => Int,a: Int,b: Int): Int = {
  def hlp(a: Int,b: Int): TailRec[Int] = {
    if (a <= 0 || b <= 0) done(p(a,b))
    else if (p(a,b) % 2 == 0) tailcall(hlp(a - 1,b)).map(p(a,_))
    else tailcall(hlp(a,b - 1)).map(p(_,b))
  }

  hlp(a,b).result
}

http://eed3si9n.com/herding-cats/stackless-scala-with-free-monads.html

http://eed3si9n.com/herding-cats/tail-recursive-monads.html

实际上,您的功能看起来并不像Ackermann。实际的Ackermann进行了两次递归调用

f(m,n) = f(m - 1,f(m,n - 1))

您的函数进行单个递归调用。编写函数的迭代版本并不难(通常使用尾部递归,因为编译器可以将其自动转换为迭代版本)。假设我们已经为ppa(i,j)0 <= i <= a - 1(黄色区域)计算了0 <= j <= b - 1。然后,我们计算两个橙色线段(a,0),(a,1),...,b - 1)(按此顺序)和(0,b),(1,(a - 1,b)(按此顺序)。然后,我们计算红色单元格(a,b)

enter image description here

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...