在存在一個簡單的修改來遞歸調用的值的情況下,該操作可被移動到遞歸函數的前部。的典型的例子是尾遞歸模缺點,其中在這種形式的簡單的遞歸函數:
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轉換將產生一個棧友好的實現,即使指數運行時仍然是一個問題。 –