我很难实现tail递归foreach,reduce,map和toList函数,以实现二叉树的非常简单的实现。
sealed trait Tree[+A]
case object EmptyTree extends Tree[Nothing]
case class Node[A](value: A, left: Tree[A], right: Tree[A]) extends Tree[A]
object Tree {
def apply[A]: Tree[A] = EmptyTree
def apply[A](value: A): Tree[A] = Node(value, EmptyTree, EmptyTree)
def apply[A](value: A, left: Tree[A], right: Tree[A]): Tree[A] = Node(value, left, right)
def foreach[A](tree: Tree[A], f: (A) => Unit): Unit = {
//@tailrec
def iter[A](tree: Tree[A], f: (A) => Unit): Unit = tree match {
case EmptyTree =>
case Node(v, l, r) =>
iter(l, f)
f(v)
iter(r, f)
}
iter(tree, f)
}
def reduce[A](tree: Tree[A], value: A, f: (A, A) => A): A = {
//@tailrec
def loop(tree: Tree[A], value: A): A = tree match {
case Node(v, l, r) => loop(l, f(loop(r, value), v))
case EmptyTree => value
}
loop(tree, value)
}
def map[A, B](tree: Tree[A], f: A => B): Tree[B] = {
//@tailrec
def iter[A](tree: Tree[A], f: A => B): Tree[B] = tree match {
case Node(v, l, r) => Node(f(v), iter(l, f), iter(r, f))
case EmptyTree => EmptyTree
}
iter(tree, f)
}
def toList[A](t: Tree[A]): List[A] = {
//@tailrec
def iter[A](t: Tree[A]): List[A] = t match {
case Node(v, l, r) => v :: iter(l) ::: iter(r)
case EmptyTree => List.empty
}
iter(t)
}
}
测试代码:
val tree = Tree(1, Tree(2, Tree(3), Tree(4)), Tree(5, Tree(6), Tree(7)))
Tree.foreach(tree, (x: Int) => println(x))
Tree.reduce(tree, 0, (x: Int, y: Int) => x + y)
Tree.map(tree, (x: Int) => x + 1)
Tree.toList(tree)
我无法使用@tailrec属性,因为正如您所看到的,递归调用不是函数中的最后一次调用,我不知道如何重写它,因为在一个函数中有多个调用,例如
v :: iter(l) ::: iter(r)
我知道我可以使用累加器来实现内部递归函数,但是在多次调用时我应该如何使用它?
提前致谢。
已更新:
def toListRec[A](tree: Tree[A]): List[A] = {
@tailrec
def iter(result: List[A], todo: List[Tree[A]]): List[A] = todo match {
case x :: tail => x match {
case Node(v, l, r) => iter(v :: result, l :: r :: tail)
case EmptyTree => iter(result, tail)
}
case Nil => result.reverse
}
iter(List.empty, List(tree))
}
答案 0 :(得分:9)
没有尾递归,(/)堆栈用于跟踪调用函数。如果你想使用尾递归,你必须找到一种在其他地方跟踪这些信息的方法。在更简单的“线性”情况下,例如阶乘,这些信息非常有限,通常可以通过累加器轻松处理。
在您的情况下,问题是递归不是线性的。在一次递归调用之后,该函数不只是计算结果,而是在能够获得结果之前进行另一次递归调用。
为了在这种情况下应用尾递归,您必须明确地跟踪必须进行的剩余递归调用。一种简单的方法是简单地保留“待办事项”列表。例如:
def toList[A](t: Tree[A]): List[A] = {
@tailrec def iter[A](todo: List[Tree[A]], r: List[A]): List[A] =
todo match {
case t :: rest => t match {
case Node(v, l, r) => iter(l :: r :: rest, v :: r)
case EmptyTree => iter(rest, r)
}
case List.empty => reverse(r)
}
iter(List(t), List.empty)
}
免责声明:我对scala一无所知。 :)
答案 1 :(得分:-2)
mweerden建议的解决方案可行,但是,还有另一种解决问题的方法,我认为这种方式更为优雅。这是遍历要列出的树的代码
def toList[T](t: Tree[T]): List[T] = {
def tailRecursive(tree: Tree[T], acc: List[T]): List[T] = tree match {
case EmptyTree => acc
case Node(value, right, left) =>
tailRecursive(left, value :: tailRecursive(right, acc))
}
tailRecursive(t, List())
}
解决方案暗示树是二叉搜索树,生成的列表将按升序排列(如果不需要升序,则可以更改第6行,将值放在第一次递归调用之前或直接进入累加器是可能的。)