避免for循环达到scala中的尾递归状态
avoiding a for loop to reach a tail recursive state in scala
我已经编写了以下方法来打印任意元数树结构。 (深度优先搜索)
def treeString[A](tree: Tree[A]): String = {
def DFS(t: Tree[A], acc: String = "", visited: List[String] = Nil, toClose: Int = 0): String = t match {
case Leaf(id, terminal) if !visited.contains(id) => {
acc + " " + terminal.name + "[" + id.toString + "] " + ")"*toClose
}
case Node(id, op, args @_*) if !visited.contains(id) => {
var state = acc + " " + op.name + "[" + id.toString + "] " + "("
var args_visited: List[String] = List(id)
for (i <- args.tail) {
state = DFS(i, state , visited ::: args_visited) + " , "
args_visited = args_visited ++ List(i.id)
}
DFS(args.head, state , visited ++ args_visited, toClose + 1)
}
case _ => acc
}
DFS(tree)
}
scala 编译器声称此函数不是 tail recursive
。但是在尾部位置我有正确的 DFS(..)
调用。循环中进行的所有 DFS(..)
调用都将以迭代方式完成,因此堆栈安全。
我知道如果树无限深就会发生堆栈溢出。我们有处理这些情况的技术吗?
我必须同意@VictorMoroz。问题是你的声明:
state = DFS(i, state , visited ::: args_visited) + " , "
不在尾部位置。它确实创建了一个新堆栈,因此,您的方法不再是尾递归了。
无需深入研究您的数据结构,这里介绍以 DFS 方式尾递归遍历树的方法。
sealed trait Tree[+A]
case class Leaf[A](value: A) extends Tree[A]
case class Node[A](left: Tree[A], right: Tree[A]) extends Tree[A]
case class Element(id:Int, name:String)
def DFS[T](t:Tree[T]): List[Element] = {
@tailrec
def _dfs(rest:List[Tree[T]], visited:List[Element]):List[Element] = {
rest match {
case Leaf(v:Element) => v :: visited
case Node(l,r) :: rs => _dfs(l::r::rs,visited)
}
}
_dfs(List(t),Nil)
}
关键是使用显式堆栈 - 这里以列表的形式。
I understand that if the tree is infinitely deep a stack overflow will
occur.
这是正确的。基本上,在大多数编程语言中,每个方法调用都会保留一些堆栈内存。简单来说,尾递归允许编译器忽略之前的状态——不需要之前的堆栈。
请记住,DFS 无法确保找到 global optima,如果应该满足则不应使用。
后记:
@tailrec
注释是对编译器的友好提示,用于在编译时检查您的方法是否可以优化(Scala 默认执行尾调用优化)。
我成功地达到了尾递归状态。
尽管代码很优雅,但我确实质疑函数式方法的 cost/benefit。大多数时候,如何通过算法手动传递堆栈并不明显。
无论如何,这里真正的问题在于 JVM 的递归问题,scala 编译器仍然试图为我们提供尾递归特性的逃避,但让事情正常运行通常是一场噩梦
def treeString[A](tree: Tree[A]): String = {
def dropWhile[A](l: List[A], f: A => Boolean): List[A] =
l match {
case h :: t if f(h) => dropWhile(t, f)
case _ => l
}
@tailrec
def DFS(toVisit: List[Tree[A]], visited: List[String] = Nil, acc: String = "", n_args: List[Int] = Nil): String = toVisit match {
case Leaf(id, terminal) :: tail if !visited.contains(id) => {
val n_args_filtered = dropWhile[Int](n_args, x => x == 1)
val acc_to_pass = acc + " " + terminal.name + "[" + id.toString + "] " + ")" * (n_args.length - n_args_filtered.length) + "," * {if (n_args_filtered.length > 0) 1 else 0}
val n_args_to_pass = {if (n_args_filtered.length > 0 )List(n_args_filtered.head - 1) ::: n_args_filtered.tail else Nil}
DFS(toVisit.tail, visited ::: List(id), acc_to_pass, n_args_to_pass)
}
case Node(id, op, args @_*) :: tail if !visited.contains(id) => {
DFS(args.toList ::: toVisit.tail, visited ::: List(id), acc + " " + op.name + " (", List(args.length ) ::: n_args)
}
case Nil => acc
}
DFS(List(tree))
}
我已经编写了以下方法来打印任意元数树结构。 (深度优先搜索)
def treeString[A](tree: Tree[A]): String = {
def DFS(t: Tree[A], acc: String = "", visited: List[String] = Nil, toClose: Int = 0): String = t match {
case Leaf(id, terminal) if !visited.contains(id) => {
acc + " " + terminal.name + "[" + id.toString + "] " + ")"*toClose
}
case Node(id, op, args @_*) if !visited.contains(id) => {
var state = acc + " " + op.name + "[" + id.toString + "] " + "("
var args_visited: List[String] = List(id)
for (i <- args.tail) {
state = DFS(i, state , visited ::: args_visited) + " , "
args_visited = args_visited ++ List(i.id)
}
DFS(args.head, state , visited ++ args_visited, toClose + 1)
}
case _ => acc
}
DFS(tree)
}
scala 编译器声称此函数不是 tail recursive
。但是在尾部位置我有正确的 DFS(..)
调用。循环中进行的所有 DFS(..)
调用都将以迭代方式完成,因此堆栈安全。
我知道如果树无限深就会发生堆栈溢出。我们有处理这些情况的技术吗?
我必须同意@VictorMoroz。问题是你的声明:
state = DFS(i, state , visited ::: args_visited) + " , "
不在尾部位置。它确实创建了一个新堆栈,因此,您的方法不再是尾递归了。
无需深入研究您的数据结构,这里介绍以 DFS 方式尾递归遍历树的方法。
sealed trait Tree[+A]
case class Leaf[A](value: A) extends Tree[A]
case class Node[A](left: Tree[A], right: Tree[A]) extends Tree[A]
case class Element(id:Int, name:String)
def DFS[T](t:Tree[T]): List[Element] = {
@tailrec
def _dfs(rest:List[Tree[T]], visited:List[Element]):List[Element] = {
rest match {
case Leaf(v:Element) => v :: visited
case Node(l,r) :: rs => _dfs(l::r::rs,visited)
}
}
_dfs(List(t),Nil)
}
关键是使用显式堆栈 - 这里以列表的形式。
I understand that if the tree is infinitely deep a stack overflow will occur.
这是正确的。基本上,在大多数编程语言中,每个方法调用都会保留一些堆栈内存。简单来说,尾递归允许编译器忽略之前的状态——不需要之前的堆栈。
请记住,DFS 无法确保找到 global optima,如果应该满足则不应使用。
后记:
@tailrec
注释是对编译器的友好提示,用于在编译时检查您的方法是否可以优化(Scala 默认执行尾调用优化)。
我成功地达到了尾递归状态。
尽管代码很优雅,但我确实质疑函数式方法的 cost/benefit。大多数时候,如何通过算法手动传递堆栈并不明显。
无论如何,这里真正的问题在于 JVM 的递归问题,scala 编译器仍然试图为我们提供尾递归特性的逃避,但让事情正常运行通常是一场噩梦
def treeString[A](tree: Tree[A]): String = {
def dropWhile[A](l: List[A], f: A => Boolean): List[A] =
l match {
case h :: t if f(h) => dropWhile(t, f)
case _ => l
}
@tailrec
def DFS(toVisit: List[Tree[A]], visited: List[String] = Nil, acc: String = "", n_args: List[Int] = Nil): String = toVisit match {
case Leaf(id, terminal) :: tail if !visited.contains(id) => {
val n_args_filtered = dropWhile[Int](n_args, x => x == 1)
val acc_to_pass = acc + " " + terminal.name + "[" + id.toString + "] " + ")" * (n_args.length - n_args_filtered.length) + "," * {if (n_args_filtered.length > 0) 1 else 0}
val n_args_to_pass = {if (n_args_filtered.length > 0 )List(n_args_filtered.head - 1) ::: n_args_filtered.tail else Nil}
DFS(toVisit.tail, visited ::: List(id), acc_to_pass, n_args_to_pass)
}
case Node(id, op, args @_*) :: tail if !visited.contains(id) => {
DFS(args.toList ::: toVisit.tail, visited ::: List(id), acc + " " + op.name + " (", List(args.length ) ::: n_args)
}
case Nil => acc
}
DFS(List(tree))
}