在scala中以Tail递归方式遍历树状对象

ElB*_*ulP 6 recursion scala tail-recursion traversal

我试图创建一个函数,以尾递归方式横向一个具有树状结构的对象,但到目前为止,我无法编写一个代码来执行它.

我的树像对象是:

case class Node(lex: String,
                position: Int,
                posTag: String,
                var dependency: Int = -1,
                var left: Vector[Node],
                var right: Vector[Node]) 
Run Code Online (Sandbox Code Playgroud)

版本1,尾递归(不工作)

到目前为止,我尝试过最简单的形式:

def matchNodes(goldSentence: LabeledSentence): Int = {

  def condition(n: Node): Boolean = ???

  @tailrec
  def match0(acc: Int, n: Seq[Node]): Int =
    (n: @switch) match {
      case head :: tail => {
        if (condition(head)) {
          match0(acc + 1, tail)
        } else {
          acc
        }
      }
      case _ => acc
    }
  match0(0, left) + match0(0, right)
}
Run Code Online (Sandbox Code Playgroud)

上面的代码是tailrec,但它不是横穿整个树,只是第一级.

版本2,尾递归(不工作)

其他方式是:

def matchNodes(goldSentence: LabeledSentence): Int = {
  @inline def condition(n: Node): Boolean = ???

  def sumAcc(nodes: Vector[Node]): Vector[Node] = nodes match {
    case head +: tail => sumAcc(head.left ++ head.right ++ tail)
    case _ => nodes
  }

  @tailrec
  def match0(acc: Int, n: Seq[Node]): Int =
    (n: @switch) match {
      case head :: tail => {
        if (condition(head)) {
          match0(acc + 1, tail)
        } else {
          acc
        }
      }
      case _ => acc
    }

  val flatTree  =  sumAcc(right ++ left)
  match0(0, flatTree)
}
Run Code Online (Sandbox Code Playgroud)

在这里,我试图将所有节点平放到一个节点Vector[Node],但由于某种原因,处理树后的预期结果不正确.

版本3,没有尾递归(工作)

我尝试的最后一个代码不是尾递归,但它是唯一一个计算正确结果的代码:

def matchNodes(goldSentence: LabeledSentence): Int = {

  var correctRoots = 0
  val position:Int = this.position
  val dep:Int = dependency
  val tag = goldSentence.tags(position)
  if (goldSentence.dep(position) == dep || Constants.punctuationTags.contains(tag))
    correctRoots += 1

  if (right.nonEmpty)
    for (r <- right)
      correctRoots += r.matchNodes(goldSentence)
  if (left.nonEmpty)
    for (l <- left)
      correctRoots += l.matchNodes(goldSentence)

  correctRoots
}
Run Code Online (Sandbox Code Playgroud)

有没有办法让这个函数尾递归?

ElB*_*ulP 6

我终于写了一个尾递归方法来横贯整个树,这里它是 @badcook 答案的替代方案:

def matchNodes(goldSentence: LabeledSentence): Int = {
  @inline def condition(n: Node): Boolean = ???

  @tailrec
  def match0(acc:Int, n: Node)(queue:Seq[Node]): Int = {
    val count = if (condition(n)) acc + 1 else acc

    (queue: @switch) match {
      case head +: tail =>
        match0(count, head)(head.left ++ head.right ++ tail)
      case Nil =>
        count
    }
  }

  match0(0, this)(left ++ right)
}
Run Code Online (Sandbox Code Playgroud)


bad*_*ook 3

没有自然的方法可以将其转换为使用尾递归(即,您不会在空间使用方面获得渐近的改进)。这就是您在这里使用的树遍历算法需要一个堆栈,无论是递归给出的调用堆栈还是您维护的显式堆栈。如果您不想破坏调用堆栈,您可以使用显式堆栈并进行迭代。如果您确实想坚持使用尾递归,则可以将堆栈作为额外的累加器参数传递。

// Simplified version of your Node; let's ignore the value for now
case class Node(value: Unit, children: List[Node])
var counter = 0
def traverseNodeAccum(node: Node)(acc: List[Node]): Unit = {
  counter += 1
  (node.children, acc) match {
    case (Nil, Nil) => 
      ()
    case (Nil, x :: rest) =>
      traverseNodeAccum(x)(rest)
    case (child :: otherChildren, xs) => 
      traverseNodeAccum(child)(otherChildren ++ xs)
  }
}
Run Code Online (Sandbox Code Playgroud)

如果您想在完全不产生堆栈成本的情况下进行遍历,则必须拥有树的可变表示(至少据我所知)。不幸的是,你的case class治疗不起作用。