在存在一个简单的修改来递归调用的值的情况下,该操作可被移动到递归函数的前部。的典型的例子是尾递归模缺点,其中在这种形式的简单的递归函数:
def recur[A](...):List[A] = {
...
x :: recur(...)
}
这不是尾递归,被变换成
def recur[A]{...): List[A] = {
def consRecur(..., consA: A): List[A] = {
consA :: ...
...
consrecur(..., ...)
}
...
consrecur(...,...)
}
Alexlv的例子是变体这个的。
这是这样一个众所周知的情况,一些编译器(我知道的序言和方案的例子,但Scalac并没有这样做),可以检测到简单的情况,并自动执行该优化。
问题多次调用相结合,递归函数都没有这样简单的解决方案。 TMRC优化无效,因为您只是将第一个递归调用移动到另一个非尾部位置。达到尾递归解决方案的唯一方法是除去其中一个递归调用。如何做到这一点完全取决于环境,但需要找到一种完全不同的方法来解决问题。
碰巧,在某些方面你的例子是类似于经典的Fibonnaci序列的问题;在这种情况下,天真但优雅的双递归解决方案可以被从第0个数字向前循环的解决方案取代。
def fib (n: Long): Long = n match {
case 0 | 1 => n
case _ => fib(n - 2) + fib(n - 1)
}
def fib (n: Long): Long = {
def loop(current: Long, next: => Long, iteration: Long): Long = {
if (n == iteration)
current
else
loop(next, current + next, iteration + 1)
}
loop(0, 1, 0)
}
对于Fibonnaci序列,这是最有效的方法(基于流的解决方案是这样的解决方案,可以缓存用于后续调用的结果的只是一个不同的表达)。不同的是,你需要缓存整个前一列 - 现在, 您也可以通过从C0循环前进/ R0(当然,C0/R2),并计算序列每一行解决您的问题。因此,虽然它与fib相似,但它在具体情况上有很大不同,并且比原始的双递归解决方案效率低得多。
这里是为您的帕斯卡三角例如一种方法,其可以有效地计算pascal(30,60)
:
def pascal(column: Long, row: Long):Long = {
type Point = (Long, Long)
type Points = List[Point]
type Triangle = Map[Point,Long]
def above(p: Point) = (p._1, p._2 - 1)
def aboveLeft(p: Point) = (p._1 - 1, p._2 - 1)
def find(ps: Points, t: Triangle): Long = ps match {
// Found the ultimate goal
case (p :: Nil) if t contains p => t(p)
// Found an intermediate point: pop the stack and carry on
case (p :: rest) if t contains p => find(rest, t)
// Hit a triangle edge, add it to the triangle
case ((c, r) :: _) if (c == 0) || (c == r) => find(ps, t + ((c,r) -> 1))
// Triangle contains (c - 1, r - 1)...
case (p :: _) if t contains aboveLeft(p) => if (t contains above(p))
// And it contains (c, r - 1)! Add to the triangle
find(ps, t + (p -> (t(aboveLeft(p)) + t(above(p)))))
else
// Does not contain(c, r -1). So find that
find(above(p) :: ps, t)
// If we get here, we don't have (c - 1, r - 1). Find that.
case (p :: _) => find(aboveLeft(p) :: ps, t)
}
require(column >= 0 && row >= 0 && column <= row)
(column, row) match {
case (c, r) if (c == 0) || (c == r) => 1
case p => find(List(p), Map())
}
}
它是有效的,但我认为这显示复杂的递归解决方案如何丑陋成为你使其变形成为尾递归。此时,完全可能需要转向不同的模型。 Continuations或monadic gymnastics可能会更好。
你需要一个通用的方法来改变你的功能。没有一个。有帮助的方法,就是这样。
很好的答案。 Rúnar的论文特别提供了信息,尽管它可能与你最终的主张矛盾(具体取决于你心目中的转变)。他的Trampoline转换将产生一个栈友好的实现,即使指数运行时仍然是一个问题。 –