在对递归调用的值进行简单修改的情况下,可以将该操作移至递归函数的前面。典型的例子是Tail recursion modulo cons,这里有一个简单的递归函数:
def recur[A](...):List[A] = {
...
x :: recur(...)
}
不是尾递归的,被转化为
def recur[A]{...): List[A] = {
def consRecur(..., consA: A): List[A] = {
consA :: ...
...
consrecur(..., ...)
}
...
consrecur(...,...)
}
Alexlv 的例子是这个的一个变种。
这是众所周知的情况,以至于一些编译器(我知道 Prolog 和 Scheme 示例,但 Scalac 不这样做)可以检测简单的情况并自动执行此优化。
将多个调用组合到递归函数的问题没有这样简单的解决方案。TMRC optimisatin 没有用,因为您只是将第一个递归调用移动到另一个非尾位置。达到尾递归解决方案的唯一方法是删除除一个递归调用之外的所有递归调用;如何做到这一点完全取决于上下文,但需要找到一种完全不同的方法来解决问题。
碰巧,您的示例在某些方面类似于经典的斐波那契数列问题;在这种情况下,可以用从第 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())
}
}
它很有效,但我认为它显示了当您将复杂递归解决方案变形为尾递归时,它们会变得多么丑陋。在这一点上,可能值得完全转向不同的模型。 延续或一元体操可能会更好。
您想要一种通用的方法来转换您的功能。没有一个。有一些有用的方法,仅此而已。