简体   繁体   English

OCaml中的尾递归

[英]Tail recursion in OCaml

I'm trying to implement merge function in OCaml using Tail recursion but I face awkward results. 我正在尝试使用Tail递归在OCaml中实现合并功能,但是我遇到了尴尬的结果。 Could anyone help me out with this. 谁能帮我这个忙。 Thanks in advance. 提前致谢。

let rec merge_helper l1 l2 accum = 
match l1 with
[] -> l2@accum
| hd::tl -> (match l2 with
    [] -> l1@accum
    |x::xs -> merge_helper tl xs l1@l2@accum);;
let merge l1 l2 = merge_helper l1 l2 [];;

merge [1;2;4] [3;4;5];;
- : int list = [4; 5; 2; 4; 4; 5; 1; 2; 4; 3; 4; 5]

First of all your implementation doesn't run in a constant stack space. 首先,您的实现不会在恒定的堆栈空间中运行。 The xs @ ys operation is not tail-recursive, and will make List.length xs calls (thus using this amount of stack frames). xs @ ys操作不是尾部递归的,它将进行List.length xs调用(因此使用此数量的堆栈帧)。 Also, the merge function usually preserves the ordering. 同样, merge功能通常会保留顺序。 So you need to have a comparison function, that will compare elements of the list. 因此,您需要具有一个比较功能,该功能将比较列表中的元素。 It is not absolutely clear, what you're expecting from you merge function, and why you classify your result as weird. 目前尚不清楚,您对merge功能的期望是什么,为什么将结果归类为怪异的。 For me the result matches with the code. 对我来说,结果与代码匹配。 What looks very strange to me is that although you're deconstructing l1 and l2 you're not using the result of the deconstruction and adds the whole lists l1 and l2 to the accumulator. 对我来说很奇怪的是,尽管您正在解构l1l2 ,但是您并未使用解构的结果,而是将整个列表l1l2到累加器中。

The approach should be the following, take an element from the first list, add this element to the accumulator, and switch the lists. 方法应如下,从第一个列表中获取一个元素,将该元素添加到累加器中,然后切换列表。 So the induction step of the algorithm is the following: 因此,该算法的归纳步骤如下:

 let rec loop acc xs ys = match xs with
   ...
   | x::xs -> loop (x::acc) ys xs

But if you want to merge two sorted lists preserving the order, then you need to take the smallest element of the two lists at each step. 但是,如果要合并两个保留顺序的列表,则需要在每个步骤中采用两个列表中最小的元素。

let merge cmp xs ys = 
    let rec loop xs ys zs = match xs,ys with
      | [],ss|ss,[] -> List.rev_append zs ss
      | x :: txs, y :: tys -> 
         if cmp x y <= 0 
         then loop txs ys (x::zs)
         else loop xs tys (y::zs) in
    loop xs ys []

Here in the induction step, we take the smaller element, and continue with the two lists: the tail of the owner of the smaller element (because it is moved into the accumulator), and the second list is taken fully (because nothing is accumulated from it). 在归纳步骤中,我们采用较小的元素,并继续两个列表:较小元素的所有者的尾巴(因为它已移至累加器中),第二个列表已被完整处理(因为未累积任何内容)从中)。 Since we're prepending elements, they will be in a reversed order, so we will need to something to reverse the result (a usual trade off for tail-recursion). 由于我们在元素之前,它们将以相反的顺序排列,因此我们需要做一些事情来反转结果(通常需要权衡尾递归)。 The base case, allows us to short-circuit our algorithm, when one or another list is shorter, and we don't need any more to compare them one-by-one, and can just append the rest part of the longer list to the accumulator zs . 基本情况允许我们在一个或另一个列表较短的情况下缩短算法,并且不再需要一个一个地比较它们,只需将较长列表的其余部分附加到累加器zs We use List.rev_append to append the leftover tail of the list to our accumulator. 我们使用List.rev_append将列表的剩余尾部追加到累加器中。 This function will prepend the reversed version of the first list to the second. 此功能会将第一个列表的反向版本放在第二个列表的前面。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM