【问题标题】:fold_tree in OCamlOCaml 中的折叠树
【发布时间】:2010-11-15 22:32:23
【问题描述】:

如您所知,OCaml 中有更高阶的函数,例如 fold_left、fold_right、filter 等。

在我的函数式编程课程中引入了名为 fold_tree 的函数,它类似于 fold_left/right,不是在列表上,而是在(二叉树)树上。它看起来像这样:

 let rec fold_tree f a t = 
  match t with
    Leaf -> a |
    Node (l, x, r) -> f x (fold_tree f a l) (fold_tree f a r);;

其中树定义为:

type 'a tree = 
  Node of 'a tree * 'a * 'a tree | 
  Leaf;;

好的,这是我的问题: fold_tree 函数是如何工作的?你能给我一些例子,用人类语言解释一下吗?

【问题讨论】:

    标签: functional-programming ocaml higher-order-functions


    【解决方案1】:

    这里有一个样式建议,将横杠放在行首。它使案件从哪里开始变得更加清晰。为了保持一致性,第一个栏是可选的,但建议使用。

    type 'a tree = 
      | Node of 'a tree * 'a * 'a tree
      | Leaf;;
    
    let rec fold_tree f a t = 
        match t with
          | Leaf -> a
          | Node (l, x, r) -> f x (fold_tree f a l) (fold_tree f a r);;
    

    至于它的工作原理,请考虑以下树:

    let t = Node(Leaf, 5, Node(Leaf, 2, Leaf));;
    

    类型为int tree

    在视觉上,t 看起来像这样:

    5 / \ () 2 / \ () ()

    调用 fold_tree,我们需要一个函数来组合这些值。基于Node 案例中的用法,f 接受 3 个参数,它们都是树的相同类型并返回相同。我们会这样做:

    let f x l r = x + l + r;; (* add all together *)
    fold_tree f 1 t;;
    

    这将有助于了解每种情况下会发生什么。对于任何 Leaf,返回 a。对于任何Node,它结合了存储的值和折叠左右子树的结果。在这种情况下,我们只是将每个叶子算作一的数字相加。这棵树的折叠结果是10

    【讨论】:

    • 感谢您提供了一个很好的例子;)。它帮助我理解了基础知识,现在我需要一些更难的东西。
    • f 接受 3 个参数,都是树的相同类型并返回相同。 一个是树的类型,另外两个是任何相同的累加器类型,与叶子匹配的默认值一致。
    • @nlucaroni:这是针对这个特定示例的措辞,但否则你是对的。
    【解决方案2】:

    让我们以树为例。

    let t = Node (Node (Leaf, 10, Leaf), 1, Node (Node (Leaf, 20, Leaf), 11, Leaf))
    

    fold 操作的一般定义是用函数替换构造函数,在树的任何地方。

    general_fold_tree node leaf t =
        node (node leaf 10 leaf) 1 (node (node leaf 20 leaf) 11 leaf)
    

    node 是一个函数,它从左 something、一个元素和一个右 something 构造一个 something,就像 @987654325 @ 是从左子树、节点内容和右子树构造树的构造函数。 leaf 是一个常量,匹配Leaf 常量构造函数。

    let rec general_fold_tree (node : 'b -> 'a -> 'b -> 'b) (leaf : 'a) (t : 'a tree) : 'b =
      let recurse t = general_fold_tree node leaf t in
      match t with
      | Node (l, x, r) -> node (recurse l) x (recurse r)
      | Leaf -> leaf
    

    请注意,函数的类型与类型定义的类型匹配,除了类型定义描述'a tree 的构建, fold 函数描述任何'b 的构建。

    看起来很像一般折叠的操作仍称为折叠。例如,在列表中,List.fold_right 是根据上述定义的一般折叠; List.fold_left 是反向应用函数的变体(fold_left 相当于 reverse + fold_right + reverse,虽然它更清晰、更有效)。

    您自己的fold_tree 是这种通用折叠的简单变体,其中节点函数以与构造函数不同的顺序获取其参数:

    let equrts_fold_tree f a t =
      let node l x r = f x l r in
      general_fold_tree node a t
    

    【讨论】:

      【解决方案3】:

      这是一种在列表中考虑fold_right 的方法:例如,列表

      (1 :: (2 :: (3 :: [])))
      

      然后你用一个新的二元运算代替 ::(和一个新的常数而不是 [])重新解释列表。

      fold_right (+) l 0 = (1 + (2 + (3 + 0)))
      

      如果您不想对列表执行任何操作,可以将函数 cons 作为函数传递,将空列表作为中性元素传递。所以从某种意义上说,fold_right 很笼统:它甚至可以让你完全不丢失任何信息。

      您问题中的fold_tree 与树的数据类型相同。如果你想重新解释你的树,你可以向它传递一个新的节点函数,而不是构造函数Node。如果你想得到一棵相同的树,你可以用Leaf 作为中性,(fun x l r -> Node (l, x, r)) 作为函数来应用它。 与列表的fold_left 类似,这个示例应用程序不是很有趣,但它意味着fold_left 是一个非常笼统的转换(技术术语是morphism)。

      例如,您还可以使用函数 (fun x l r -> x + l + r) 对树的元素求和。

      【讨论】:

        【解决方案4】:

        【讨论】:

          【解决方案5】:

          看来f是一个三参数归约函数,a是我们归约的中性元素,t是根,所以:

          给定一个像这样的二进制文件(我不太记得变体类型的语法,所以请在这里屈尊俯就)

          let r = Node(Node(Node(Leaf,3,Leaf),2,Node(Leaf,4,Leaf)),1,Node(Node(Leaf,6,Leaf),5,Node(Leaf,7,Leaf)))
          

          如果你想对所有节点求和,函数的调用方式如下:

          let add x y z = x + y + z
          fold_tree add 0 r
          

          我们将t 匹配为一个节点,所以我们有:

          (add 1 (fold_tree add 0 Node(Node(Leaf,3,Leaf),2,Node(Leaf,4,Leaf))) (fold_tree add 0 Node(Node(Leaf,6,Leaf),5,Node(Leaf,7,Leaf))))
          

          如果我们再扩展一点,我们会得到:

          (add 1 (add 2 (fold_tree add 0 Node(Leaf,3,Leaf)) (fold_tree add 0 Node(Leaf,4,Leaf))) (add 5 (fold_tree add 0 Node(Leaf,6,Leaf)) (fold_tree add 0 Node(Leaf,7,Leaf))))
          

          再一次,我们正在匹配叶子:

          (add 1 (add 2 (add 3 0 0) (add 4 0 0)) (add 5 (add 6 0 0) (add 7 0 0))
          (add 1 (add 2 3 4) (add 5 6 7))
          (add 1 9 18)
          

          最终得到:

          28
          

          希望对你有帮助。

          【讨论】:

          • OP 的 fold_tree 函数将三元函数作为参数,因此 (+) 不会削减它。
          • 是的,我在写第一个函数时正在考虑 Lisp,但我已经修复了它:-p
          • 在 OPs 数据类型中,树的叶子也没有附加数据。并且 Node 构造函数的参数顺序错误。
          • 哎呀,我现在已经看到 Node 结构的顺序是错误的,但我没有修复它!XDDDD
          猜你喜欢
          • 1970-01-01
          • 2020-03-19
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-01-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多