Functional programming 在OCaml中折叠树
您可能知道,OCaml中有一些高阶函数,例如fold_left、fold_right、filter等 在我的函数编程课程中,介绍了名为fold_tree的函数,它类似于fold_left/right,不是在列表上,而是在(二进制)树上。看起来是这样的: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
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