以下是两个列表的append
的递归版本:
def append[A](x: List[A], y: List[A]): List[A] = x match {
case Nil => y
case h :: t => h :: append(t, y)
}
如何将其转换为尾递归版?
答案 0 :(得分:3)
试试这个,尽管x以相反的顺序排在前面。这是打算吗?
import scala.annotation.tailrec
@tailrec
def append[A](x: List[A], y: List[A]): List[A] = x match {
case Nil => y
case h :: t => append(t, h :: y)
}
如果你想按照它的顺序加上x,你必须做这样的事情:
import scala.annotation.tailrec
def append[A](x: List[A], y: List[A]): List[A] = {
@tailrec
def innerAppend[A](x: List[A], y: List[A]): List[A] = x match {
case Nil => y
case h :: t => innerAppend(t, h :: y)
}
innerAppend(x.reverse, y)
}
答案 1 :(得分:2)
Scala标准库的序列foldLeft
方法是一种尾递归方法,允许创建许多核心序列操作,这些操作在其元素之间应用二进制操作。
使用foldLeft
,以下是您实施追加方法的方法:
def append[A](x: List[A], y: List[A]): List[A] =
x.reverse.foldLeft(y)((t, h) => h :: t) //Note the swapped order for h and t
将返回附加列表,其顺序与包含reverse
:
append(List(1,2,3), List(4,5,6))
res0: List[Int] = List(1, 2, 3, 4, 5, 6)
缺少@tailrec
注释并不意味着这不是尾递归;相反,@tailrec
在通过尾随递归方法(如foldLeft
)实现方法或函数时,将无法正确检测到。
与foldLeft
不同,标准库中的foldRight
将不创建尾递归方法,尽管其语法比必须反转输入列表更容易理解交换参数。这就是为什么我更喜欢实现foldRightViaFoldLeft
方法,因为它具有更加用户友好的语法,并保留尾递归属性:
def foldRVL[A,B](l: List[A], z: B)(f: (A,B) => B): B = //"RVL" == "Right Via Left"
l.reverse.foldLeft(z)((b, a) => f(a, b)) //Note again that we are swapping a and b
def append[A](x: List[A], y: List[A]): List[A] =
foldRVL(x, y)(_ :: _) //the swap in foldRVL means that we don't have swap when using it
这仍然是尾递归,但现在更容易阅读。 foldRVL
和其他fold
方法有很多种用途。尽可能使用它们。有关折叠的更多信息以及有关其递归属性的更好解释,我建议您阅读以下文章:Scala Code Review: foldLeft and foldRight。
我也强烈推荐这本书, Scala中的函数编程,从中我学会了如何正确地fold
。