【问题标题】:Scala tree recursive fold methodScala树递归折叠方法
【发布时间】:2015-03-05 16:16:57
【问题描述】:

给定以下对(非二叉树)树的定义:

sealed trait Tree[+A]
case class Node[A](value: A, children: List[Node[A]]) extends Tree[A]
object Tree {...}

我写了下面的fold方法:

def fold[A, B](t: Node[A])(f: A ⇒ B)(g: (B, List[B]) ⇒ B): B =
  g(f(t.value), t.children map (fold(_)(f)(g)))

这可以很好地用于(除其他外)这个 ma​​p 方法:

def map[A, B](t: Node[A])(f: A ⇒ B): Node[B] =
  fold(t)(x ⇒ Node(f(x), List()))((x, y) ⇒ Node(x.value, y))

问题:有人可以帮我写一下上面 fold 的尾递归版本吗?

【问题讨论】:

    标签: scala tree tail-recursion


    【解决方案1】:

    我相信您需要一个堆栈来进行这样的遍历,就像在命令式编程中一样,如果没有递归方法,就没有自然的方式来编写它。当然,您始终可以自己管理堆栈,将堆栈移动到堆中,并防止堆栈溢出。这是一个例子:

    sealed trait Tree[+A]
    case class Node[+A](value: A, children: List[Node[A]]) extends Tree[A]
    
    case class StackFrame[+A,+B](
      value: A, 
      computedChildren: List[B], 
      remainingChildren: List[Node[A]])
    
    def fold[A,B](t: Node[A])(f: A => B)(g: (B, List[B]) => B) : B = {
    
      def go(stack: List[StackFrame[A,B]]) : B = stack match {
        case StackFrame(v, cs, Nil) :: tail => 
          val folded = g(f(v), cs.reverse)
          tail match {
            case Nil => folded
            case StackFrame(vUp, csUp, remUp) :: rest => 
              go(StackFrame(vUp, folded::csUp, remUp)::rest)
          }
        case StackFrame(v, cs, nextChild :: others) :: tail =>
          go(
            StackFrame(nextChild.value, Nil, nextChild.children) ::
            StackFrame(v, cs, others) :: 
            tail)
        case Nil => sys.error("Should not go there")
      }
    
      go(StackFrame(t.value, Nil,  t.children) :: Nil)    
    }
    

    注意:我创建了Node 协变,不是绝对必要的,但如果不是,您需要在某些地方明确指定几个Nil 的类型(例如替换为List[X]())。

    go 它显然是尾递归的,但这只是因为它自己管理堆栈。

    您可能会在this nice blog post 中找到一种基于延续和蹦床的更有原则和系统的技术(但一开始并不容易掌握)。

    【讨论】:

    • 非常清晰(有效),我也冒险进入了博文和相关paper。迷人但困难的类固醇累加器......所以我并没有低估这个问题,但让我说,从“用户”的角度来看,编写一个非尾递归单线并让编译器找到它是一个梦想优化的最佳解决方案。
    • 代码可以稍微优化一下,将 cs 替换为 cs.reverse 和 csUp :+ folded 到 folded :: csUp
    • 不是 csUp :+ 折叠 O(n) 吗?
    • 还有一点需要注意的是,您的结构的规范折叠将是 fold(Node[A])((A, List[B] => B) 而不是您的具有两个功能的变体。只有一种形状,所以折叠只需要一个功能。你会发现你可以很容易地用这个来表达你的签名,但反过来就不行了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-03-19
    • 2012-07-24
    • 2020-07-20
    • 2019-04-27
    • 2016-10-29
    • 2021-01-30
    • 1970-01-01
    相关资源
    最近更新 更多