Scala 将普通递归转换为尾部递归
我想知道是否有一些通用方法可以转换“普通”递归,将Scala 将普通递归转换为尾部递归,scala,recursion,tail-recursion,pascals-triangle,Scala,Recursion,Tail Recursion,Pascals Triangle,我想知道是否有一些通用方法可以转换“普通”递归,将foo(…)+foo(…)作为尾部递归的最后一个调用 例如(scala): 函数语言将递归函数转换为等效尾部调用的通用解决方案: 一种简单的方法是将非尾部递归函数包装在Trampolinemonad中 def pascalM(c: Int, r: Int): Trampoline[Int] = { if (c == 0 || c == r) Trampoline.done(1) else for { a <- Trampol
foo(…)+foo(…)
作为尾部递归的最后一个调用
例如(scala):
函数语言将递归函数转换为等效尾部调用的通用解决方案: 一种简单的方法是将非尾部递归函数包装在
Trampoline
monad中
def pascalM(c: Int, r: Int): Trampoline[Int] = {
if (c == 0 || c == r) Trampoline.done(1)
else for {
a <- Trampoline.suspend(pascal(c - 1, r - 1))
b <- Trampoline.suspend(pascal(c, r - 1))
} yield a + b
}
val pascal = pascalM(10, 5).run
def pascalM(c:Int,r:Int):蹦床[Int]={
如果(c==0 | | c==r)蹦床。完成(1)
其他的{
a是的,这是可能的。通常通过一些内部定义的函数使用累加器模式来完成,该函数还有一个附加参数,即所谓的累加器逻辑,例如一个列表的计数长度
例如,正常递归版本如下所示:
def length[A](xs: List[A]): Int = if (xs.isEmpty) 0 else 1 + length(xs.tail)
这不是尾部递归版本,为了消除最后的加法操作,我们必须以某种方式累积值,例如使用累加器模式:
def length[A](xs: List[A]) = {
def inner(ys: List[A], acc: Int): Int = {
if (ys.isEmpty) acc else inner(ys.tail, acc + 1)
}
inner(xs, 0)
}
编写代码的时间要长一点,但我想我的想法是清楚的。当然,你可以不用内部函数来编写,但在这种情况下,你应该手动提供acc
初始值。我很确定不可能像一般情况下那样简单,但这取决于你允许更改的详细程度是的
尾部递归函数必须可以作为while循环重新写入,但请尝试实现,例如使用while循环。这是可能的,但您需要使用数组或集合来存储每个点的状态,以替代以其他方式存储在调用堆栈中的数据
也可以使用。我不知道这个问题有多理论,但递归实现即使使用尾部递归也不会有效。例如,尝试计算pascal(30,60)
。我认为不会出现堆栈溢出,但要准备好长时间的休息时间
取而代之的是,考虑使用<代码>流<代码>或:
在对递归调用的值进行简单修改的情况下,可以将该操作移到递归函数的前面。这方面的经典示例是尾部递归模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 Optimatin是无用的,因为您只是将第一个递归调用移动到另一个非尾部位置。达到尾部递归解决方案的唯一方法是删除除一个递归调用以外的所有递归调用;如何做到这一点完全取决于上下文,但需要他们正在寻找一种完全不同的方法来解决这个问题
碰巧,在某些方面,您的示例类似于经典的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三角形示例的一种方法,它可以有效地计算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())
}
}
def pascal(列:长,行:长):长={
类型点=(长,长)
类型点=列表[点]
类型三角形=地图[点,长]
上面的定义(点)=(点1,点2-1)
左上方的def(p:点)=(p._1-1,p._2-1)
def find(ps:点,t:三角形):长=ps匹配{
//找到了最终的目标
如果t包含p=>t(p),则为case(p::Nil)
//找到一个中间点:弹出堆栈并继续
如果t包含p=>find(rest,t),则为case(p::rest)
//点击三角形边,将其添加到三角形中
格((c,r)::)如果(c==0)| |(c==r)=>find(ps,t+((c,r)->1))
//三角形包含(c-1,r-1)。。。
如果t包含左上角(p)=>如果(t包含左上角(p))
//它包含(c,r-1)!加在三角形上
求(ps,t+(p->(t(左上(p))+t(左上(p‘‘)’))
其他的
//不包含(c,r-1)。所以找到
查找(高于(p)::ps,t)
//如果我们到了这里,我们没有(c-1,r-1)。找到它。
案例(p::)=>find(左上(p)::ps,t)
}
要求(列>=0和行>=0和列1
case p=>find(List(p),Map())
}
}
它是有效的,但我认为它显示了当你将复杂的递归解决方案变形为尾部递归时,它们会变得多么丑陋。在这一点上,它可能值得完全转移到另一个模型。或者可能更好
您需要一种通用的方法来转换函数。没有。只有一些有用的方法。这确实是可能的。我的方法是 从列表(1)开始,不断递归,直到到达 你要划船。 值得注意的是,您可以对其进行优化:如果c==0或c==r,则值为1,并且要计算第100行的第3列,您仍然只需要计算前三行的前三个元素。 有效的尾部递归解决方案如下所示: def pascal(c:Int,r:Int):Int={ @泰勒克 def pascalAcc(c:Int,r:Int,acc:List[Int]):List[Int]={ 如果(r==0)acc else pascalAcc(c,r-1, //比如说从13到31
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)
}
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())
}
}
def pascal(c: Int, r: Int): Int = {
def pascalAcc(acc:Int, leftover: List[(Int, Int)]):Int = {
if (leftover.isEmpty) acc
else {
val (c1, r1) = leftover.head
// Edge.
if (c1 == 0 || c1 == r1) pascalAcc(acc + 1, leftover.tail)
// Safe checks.
else if (c1 < 0 || r1 < 0 || c1 > r1) pascalAcc(acc, leftover.tail)
// Add 2 other points to accumulator.
else pascalAcc(acc, (c1 , r1 - 1) :: ((c1 - 1, r1 - 1) :: leftover.tail ))
}
}
pascalAcc(0, List ((c,r) ))
}