Functional programming 在OCaml中折叠树

Functional programming 在OCaml中折叠树,functional-programming,ocaml,higher-order-functions,Functional Programming,Ocaml,Higher Order Functions,您可能知道,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

您可能知道,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);;
5 / \ () 2 / \ () () 其中,树定义为:

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

好的,我的问题是:折叠树函数是如何工作的?你能给我举一些例子并用人类语言解释吗?

似乎
f
是一个三参数约简函数,
a
是我们约简的中性元素,
t
是根,所以:

给定一个类似的二进制代码(我记不太清楚变量类型的语法,所以请在这里居高临下)

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

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

希望有帮助。

这里有一个风格建议,把横条放在这行的开头。它使案件从何处开始变得更加清楚。为了保持一致性,第一个栏是可选的,但建议使用

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
如下所示:

 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);;
5 / \ () 2 / \ () ()
这将有助于了解每种情况下会发生什么。对于任何
,将返回一个。对于任何
节点
,它将存储的值与折叠左右子树的结果组合在一起。在本例中,我们只是将每个叶数相加。这棵树的折叠结果是
10

下面是一种思考列表上的
右折叠
的方法:例如,列表就是

(1 :: (2 :: (3 :: [])))
然后用一个新的二进制操作代替::(并用一个新常量代替[])重新解释列表

如果您不想对列表执行任何操作,可以将函数
cons
作为函数传递,将空列表作为中性元素传递。所以从某种意义上说,
fold__right
非常通用:它甚至允许您不丢失任何信息

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


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

让我们以树为例

let t = Node (Node (Leaf, 10, Leaf), 1, Node (Node (Leaf, 20, Leaf), 11, Leaf))
操作的一般定义是,在树中的任意位置,用函数替换构造函数

general_fold_tree node leaf t =
    node (node leaf 10 leaf) 1 (node (node leaf 20 leaf) 11 leaf)
node
是从左某物、元素和右某物构造某物的函数,就像
node
是从左子树、节点内容和右子树构造树的构造函数一样
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
请注意,函数的类型与类型定义的类型相匹配,除了类型定义描述了树的构建,折叠函数描述了任何树的构建

看起来很像普通折叠的操作仍然称为折叠。例如,在列表上,
List.fold\u right
是根据上述定义的一般折叠<代码>列表。左折是一种变体,它反过来应用该功能(
左折
相当于反转+
右折
+反转,尽管它更清晰、更有效)

您自己的
fold_树
是这个普通fold的一个简单变体,其中节点函数以与构造函数不同的顺序获取其参数:

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

你可能喜欢读书


这是在F#中,但F#与OCaml非常相似。

OP的
折叠树
函数采用三元函数作为参数,因此
(+)
不会对其进行剪切。是的,我在编写第一个函数时正在考虑Lisp,但是我已经修复了它:-p在OPs数据类型中,树的叶子上也没有附加数据。节点构造函数的参数的顺序是错误的。哎哟,我现在看到节点结构的顺序是错误的,但我没有纠正它!XDDDD感谢您提供了一个很好的示例;)。它帮助我理解了基础知识,现在我需要一些更难的东西。f接受3个参数,都是相同类型的树,并且返回相同的结果。一个是树的类型,另两个是任何相同类型的累加器,与叶匹配的默认值一致。@nlucaroni:这是为这个特定的示例编写的,但在其他方面你是对的。
let equrts_fold_tree f a t =
  let node l x r = f x l r in
  general_fold_tree node a t