避免for循环在scala中达到尾递归状态

时间:2016-12-25 22:35:58

标签: scala tree tail-recursion tree-traversal graph-traversal

我编写了以下方法来打印任意的arity树结构。 (深度搜索)

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(..)调用都将以迭代方式完成,因此堆栈安全。

据我所知,如果树无限深,则会发生堆栈溢出。我们有技术处理这些案件吗?

2 个答案:

答案 0 :(得分:2)

我必须同意@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)
  }

关键是使用显式堆栈 - 这里以List的形式。

  

我明白如果树无限深,堆栈溢出就会   发生。

这是对的。基本上,在大多数编程语言中,每个方法调用都会保留一些堆栈内存。简单来说,tail-recursion允许编译器忽略先前的状态 - 不需要前一个堆栈。

请注意,DFS无法确保找到global optima,如果应该满足,则不应使用。

Postscriptum: @tailrec注释是编译器的友好提示,用于检查编译时是否可以优化您的方法(默认情况下Scala会进行尾调用)。

答案 1 :(得分:1)

我设法达到尾递归状态。

尽管代码优雅但我真正质疑功能方法的成本/收益。大多数时候,如何通过算法手动传递堆栈并不明显。

无论如何,这里的真正问题在于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))
}