两种不同类型的OCaml递归函数

时间:2018-10-06 11:04:46

标签: recursion ocaml fold

在两个星期内,我一直在OCaml中做一些简单的程序。我注意到,当我们使用递归结构T并希望在I上拥有信息T时,根据信息I我们有两种类型递归函数。

为简单起见,我们假设T是一个二叉树。因此,我将使用以下类型:

type 'a tree = Empty | 'a * 'a tree * 'a tree

现在假设信息I可以在二叉树上从左到右计算。当我从左到右说时,这意味着信息I可以从根到叶子进行计算而不会向后移动。

更清楚地说,我们要拥有的信息I就是“二叉树的节点数”。那么,利用此信息可以解决的问题是,当我们到达所有叶子时,我们得到I,因此我们从左到右从左开始,然后递归地扩展到左右子树和最终的情况是我们到达叶子时。

所以我们只有:

let rec nodes = function
    |Empty -> 0 (*it's ok we are done here*)
    |Node(_,l,r) -> 1 + nodes l + nodes r

很好的是,当可以从左到右计算信息时,OCaml的模式匹配是一个非常强大的工具,并且信息I可以轻松地计算出来。 因此,更普遍的是:

let rec get_information = function
    | Empty     -> (*here we are done so we return a constant value*)
    |Node(_,l,r)-> (*here we apply recusrively the function to the left and right tree*)

现在我的问题来了。假设I是不能从左到右而是从右到左计算的信息。因此,这意味着要获取信息I,我们需要从树的叶子开始,然后递归地扩展到顶部,只有在到达二叉树的根时才完成操作(因此,最终情况是当我们到达二叉树的根而不是叶子时。

例如,假设信息I为:“二叉树具有这样的特性:对于每个节点,其左子树中的节点数严格大于其右子树中的节点数”。如果要在线性时间内解决此问题,则需要从叶子开始,然后递归扩展到顶部(请注意,我不一定要解决问题)。

所以对我来说,编写一个获取信息I的函数非常困难,而I是从右到左的信息(它需要从叶子开始并延伸到顶部)。相反,当信息是从左到右的信息时,模式匹配是完美的。

所以我的问题是当我们需要编写一个获取信息I的函数(I从右到左时)该怎么做?是否有解决此类问题的技术?仍然有可能以棘手的方式使用模式匹配以获得所需的结果吗?

1 个答案:

答案 0 :(得分:4)

模式匹配对于编写两种功能都非常有用。也可以使用称为folds的高阶函数。

首先,是一个具体版本。我们将想知道一棵树是否左倾,如果有的话,它有多少个节点。 int option可以很好地表示这一点,其中None表示任何非左倾的树。

type 'a tree = Empty | Branch of 'a * 'a tree * 'a tree

let rec tree_info = function
  | Empty -> Some 0
  | Branch (_, l, r) ->
    match tree_info l, tree_info r with
    | Some x, Some y when x >= y -> Some (x + y + 1)
    | _ -> None

let is_left_leaning tree =
  match tree_info tree with
  | Some _ -> true
  | None -> false

(请注意,条件x >= y并非“严格大于”,但这是故意的; x > y是一个糟糕的选择。我将找出原因来做练习。)

我们还可以通过称为右折的操作来表达这种功能。对于此操作,将为要折叠的数据类型的每个构造函数提供一个值:在构造函数出现的每个位置,fold操作将使用该值来计算折叠的结果:

let rec foldr empty branch = function
  | Empty -> empty
  | Branch (x, l, r) ->
    branch x (foldr empty branch l) (foldr empty branch r)

请注意,empty值和Empty构造函数具有相同的稀疏度,而branch值和Branch构造函数具有相同的稀疏度,具有对应的参数类型。这是右折的特征。

鉴于foldr,我们可以轻松定义map

let map f tree =
  foldr Empty (fun x l r -> Branch (f x, l, r)) tree

或者当然是'tree_info':

let tree_info tree =
  foldr
    (Some 0)
    (fun _ l r ->
       match l, r with
       | Some x, Some y when x >= y -> Some (x + y + 1)
       | _ -> None)
    tree

这是tree的构造函数上模式匹配的替代方法。