簡體   English   中英

在Scala中的二叉樹上的尾遞歸折疊

[英]Tail recursive fold on a binary tree in Scala

我試圖找到二叉樹的尾遞歸折疊函數。 鑒於以下定義:

// From the book "Functional Programming in Scala", page 45
sealed trait Tree[+A]
case class Leaf[A](value: A) extends Tree[A]
case class Branch[A](left: Tree[A], right: Tree[A]) extends Tree[A]

實現非尾遞歸函數非常簡單:

def fold[A, B](t: Tree[A])(map: A => B)(red: (B, B) => B): B =
  t match {
    case Leaf(v)      => map(v)
    case Branch(l, r) => 
      red(fold(l)(map)(red), fold(r)(map)(red))
  }

但現在我正在努力尋找尾遞歸折疊函數,以便可以使用注釋@annotation.tailrec

在我的研究過程中,我發現了一些例子,其中樹上的尾遞歸函數可以例如使用自己的堆棧計算所有葉子的總和,然后基本上是List[Tree[Int]] 但據我所知,在這種情況下它只適用於添加,因為無論您是首先評估運算符的左側還是右側都不重要。 但對於廣義折疊來說,它是非常相關的。 為了表明我的意圖,這里有一些示例樹:

val leafs = Branch(Leaf(1), Leaf(2))
val left = Branch(Branch(Leaf(1), Leaf(2)), Leaf(3))
val right = Branch(Leaf(1), Branch(Leaf(2), Leaf(3)))
val bal = Branch(Branch(Leaf(1), Leaf(2)), Branch(Leaf(3), Leaf(4)))
val cmb = Branch(right, Branch(bal, Branch(leafs, left)))
val trees = List(leafs, left, right, bal, cmb)

基於這些樹,我想用給定的折疊方法創建一個深層副本,如:

val oldNewPairs = 
  trees.map(t => (t, fold(t)(Leaf(_): Tree[Int])(Branch(_, _))))

然后證明所有創建的副本的平等條件都適用:

val conditionHolds = oldNewPairs.forall(p => {
  if (p._1 == p._2) true
  else {
    println(s"Original:\n${p._1}\nNew:\n${p._2}")
    false
  }
})
println("Condition holds: " + conditionHolds)

有人可以給我一些指示嗎?

您可以在ScalaFiddle中找到此問題中使用的代碼: https ://scalafiddle.io/sf/eSKJyp2/15

如果停止使用函數調用堆棧並開始使用由代碼和累加器管理的堆棧,則可以達到尾遞歸解決方案:

def fold[A, B](t: Tree[A])(map: A => B)(red: (B, B) => B): B = {

  case object BranchStub extends Tree[Nothing]

  @tailrec
  def foldImp(toVisit: List[Tree[A]], acc: Vector[B]): Vector[B] =
    if(toVisit.isEmpty) acc
    else {
      toVisit.head match {
        case Leaf(v) =>
          val leafRes = map(v)
          foldImp(
            toVisit.tail,
            acc :+ leafRes
          )
        case Branch(l, r) =>
          foldImp(l :: r :: BranchStub :: toVisit.tail, acc)
        case BranchStub =>
          foldImp(toVisit.tail, acc.dropRight(2) ++   Vector(acc.takeRight(2).reduce(red)))
      }
    }

  foldImp(t::Nil, Vector.empty).head

}

我們的想法是從左到右累積值,通過引入存根節點跟蹤父母關系,並在探索中找到存根節點時使用累加器的最后兩個元素使用red函數減少結果。

此解決方案可以進行優化,但它已經是尾遞歸函數實現。

編輯:

通過將累加器數據結構更改為看作堆棧的列表,可以略微簡化:

def fold[A, B](t: Tree[A])(map: A => B)(red: (B, B) => B): B = {

  case object BranchStub extends Tree[Nothing]

  @tailrec
  def foldImp(toVisit: List[Tree[A]], acc: List[B]): List[B] =
    if(toVisit.isEmpty) acc
    else {
      toVisit.head match {
        case Leaf(v) =>
          foldImp(
            toVisit.tail,
            map(v)::acc 
          )
        case Branch(l, r) =>
          foldImp(r :: l :: BranchStub :: toVisit.tail, acc)
        case BranchStub =>
          foldImp(toVisit.tail, acc.take(2).reduce(red) :: acc.drop(2))
      }
    }

  foldImp(t::Nil, Nil).head

}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM