简体   繁体   English

如何转换此函数以使用尾调用

[英]How to convert this function to use tail call

Recursive function: 递归函数:

let rec listMerge (l1 : 'a list) (l2 : 'a list) =
    if l1.IsEmpty then      l2 
    elif l2.IsEmpty then    l1 
    else                    l1.Head :: l2.Head :: listMerge l1.Tail l2.Tail

Now, unless I am happily mistaken, this does not actually perform tail call, it just may look like it, if not considering that :: is right associative. 现在,除非我高兴错误,否则这实际上并不执行尾调用,它可能看起来像它,如果不考虑::是正确的关联。

Then, I am under impression (from something I read, but couldn't find now) that this can easily be converted to tail recursive by using an extra fun or something. 然后,我的印象(从我读过的东西,但现在找不到),这可以通过使用额外的fun或其他东西轻松转换为尾递归。

So, is it possible? 那么,有可能吗? Code? 码?

My answer: So, this is how I changed the functions, thanks to answers below: 我的回答:所以,这就是我改变功能的方式,感谢下面的答案:

let listMerge l1 l2 =
    let rec mergeLoop  (l1 : 'a list) (l2 : 'a list) acc =
        if l1.IsEmpty then      (List.rev acc) @ l2 
        elif l2.IsEmpty then    (List.rev acc) @ l1
        else                    mergeLoop l1.Tail l2.Tail (l2.Head :: l1.Head :: acc)
    mergeLoop l1 l2 []

As @Ramon suggested, you should use pattern matching for better readability: 正如@Ramon建议的那样,您应该使用模式匹配来提高可读性:

let rec listMerge xs ys =
    match xs, ys with
    | [], _ -> ys
    | _, [] -> xs
    | x::xs', y::ys' -> x::y::listMerge xs' ys'

As you can see, two cons constructors (::) are the last operations on listMerge so the function isn't tail-recursive. 正如您所看到的,两个cons构造函数(::)listMerge上的最后一个操作,因此该函数不是尾递归的。

You can use an accumulator to obtain results in a tail-recursive way: 您可以使用累加器以尾递归方式获取结果:

let listMerge xs ys =
    let rec loop xs ys acc =
        match xs, ys with
        | [], zs | zs, [] -> (List.rev zs)@acc
        | x::xs', y::ys' -> loop xs' ys' (y::x::acc)
    List.rev (loop xs ys [])

In the function above, the first List.rev call could be avoided if you add a few more patterns to deconstruct two lists until both of them are empty. 在上面的函数中,如果您添加一些模式来解构两个列表,直到它们都为空,则可以避免第一个List.rev调用。

In F#, there is a tail-recursive approach using sequence expressions which is along the line of continuation-passing style : 在F#中,有一个使用序列表达式的尾递归方法,它沿着连续传递样式

let listMerge xs ys =
    let rec loop xs ys =
        seq {
            match xs, ys with
            | [], zs | zs, [] -> yield! zs
            | x::xs', y::ys' -> 
                yield x
                yield y
                yield! loop xs' ys'
        }
    loop xs ys |> Seq.toList

I like this approach since it is convenient and close to your original formulation. 我喜欢这种方法,因为它方便且接近您的原始配方。

You can accumulate the constructed result in subsequent calls to listMerge and finally return the accumulated result. 您可以在后续调用listMerge累积构造结果,最后返回累积结果。 My F# skills are pretty rusted, but here goes a simple Lisp function. 我的F#技能非常生锈,但这里有一个简单的Lisp功能。

(defun list-merge (xs ys &optional acc)
  (cond ((< 0 (length xs)) (list-merge (rest xs) ys (cons (first xs) acc)))
        ((< 0 (length ys)) (list-merge xs (rest ys) (cons (first ys) acc)))
        (t acc)))

(list-merge '(1 2 3) '(3 4 5)) ;=> (5 4 3 3 2 1)
(list-merge '() '(1 2))        ;=> (2 1)
(list-merge '() '())           ;=> nil

Simple version using an accumulator: 使用累加器的简单版本:

let rec listMerge (l1 : 'a list) (l2 : 'a list) acc =
    if l1.IsEmpty then      (List.rev l2)@acc 
    elif l2.IsEmpty then    (List.rev l1)@acc
    else                    listMerge l1.Tail l2.Tail (l1.Head :: l2.Head :: acc)

I tested this with two million element lists and there was no stack overflow so I am reasonably confident this is tail recursive. 我用200万个元素列表测试了这个,没有堆栈溢出,所以我有理由相信这是尾递归。

I think you have to reuse the original F# code found in F# PowerPack . 我认为你必须重用F#PowerPack中的原始F#代码。
In fact, what you need is List.fold2 , except the function should not throw an exception SR.listsHadDifferentLengths in case if the list sizes are different, but instead process the remainder of the longer list, like this: 实际上,你需要的是List.fold2 ,除非函数不应该抛出异常SR.listsHadDifferentLengths ,以防列表大小不同,而是处理更长列表的其余部分,如下所示:

let l1 = ["A1"; "A2"; "A3"; "A4"; "A5"; "A6"; "A7"]
let l2 = ["B1"; "B2"; "B3"; "B4"]

let expectedResult = ["A1"; "B1"; "A2"; "B2"; "A3"; "B3"; "A4"; "B4"; "A5"; "A6"; "A7"]

Here's how we do it: 我们是这样做的:

[<CompiledName("Fold2Tail")>]
let fold2Tail<'T1,'T2,'State> f g1 g2 (acc:'State) (list1:list<'T1>) (list2:list<'T2>) = 
    let f  = OptimizedClosures.FSharpFunc<_,_,_,_>.Adapt(f)
    let g1 = OptimizedClosures.FSharpFunc<_,_,_>.Adapt(g1)
    let g2 = OptimizedClosures.FSharpFunc<_,_,_>.Adapt(g2)
    let rec loop acc list1 list2 =
        match list1, list2 with 
        | [], [] -> acc
        | _,  []  -> g1.Invoke(acc, list1)
        | [], _   -> g2.Invoke(acc, list2)
        | (h1::t1),(h2::t2) -> loop (f.Invoke(acc,h1,h2)) t1 t2
    loop acc list1 list2

g1 and g2 are predicates of type 'State -> 'T1 list -> 'State and 'State -> 'T2 list -> 'State , correspondingly. g1g2是类型'State -> 'T1 list -> 'State'State -> 'T2 list -> 'State谓词,相应地。 They tell how to process the remainders of both lists. 他们告诉我们如何处理两个列表的剩余部分。 Note there are two of them, since in general case 'T1 and 'T2 are different types. 注意,有两个,因为一般情况下'T1'T2是不同的类型。 Yes, it is somewhat overhead, but you can easily reduce it to your needs, sacrificing universality. 是的,它有点开销,但您可以轻松地将其降低到您的需求,牺牲普遍性。

Usage: 用法:

let ret =
    fold2Tail
        (fun s x y  -> [ yield! s; yield x; yield y ] ) // f
        List.append // g1
        List.append // g2
        []          // 'State
        l1 l2

You should use pattern matching: 你应该使用模式匹配:

let rec merge xs ys =
  match xs, ys with
  | [], xs | xs, [] -> xs
  | x::xs, y::ys -> x::y::merge xs ys

To get tail calls you can use an accumulator: 要获得尾调用,您可以使用累加器:

let merge xs ys =
  let rec loop xys xs ys =
    match xs, ys with
    | [], xs | xs, [] -> List.fold (fun xs x -> x::xs) xs xys
    | x::xs, y::ys -> loop (y::x::xys) xs ys
  loop [] xs ys

or continuation passing style: 或继续传递风格:

let merge xs ys =
  let rec loop xs ys k =
    match xs, ys with
    | [], xs | xs, [] -> k xs
    | x::xs, y::ys -> loop xs ys (fun xys -> k(x::y::xys))
  loop xs ys id

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

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