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

时间:2012-10-31 08:34:01

标签: function recursion f# functional-programming tail-recursion

递归函数:

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

现在,除非我高兴错误,否则这实际上不会执行尾调用,如果不考虑::是正确关联的话,它可能看起来像。

然后,我的印象(从我读过的东西,但现在找不到),这可以通过使用额外的fun或其他东西轻松转换为尾递归。

那么,有可能吗?码?

我的回答:所以,这就是我改变功能的方式,感谢下面的答案:

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 []

5 个答案:

答案 0 :(得分:14)

正如@Ramon建议的那样,您应该使用pattern matching来提高可读性:

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

如您所见,两个cons构造函数(::)listMerge上的最后一个操作,因此该函数不是尾递归的。

您可以使用累加器以尾递归方式获取结果:

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 [])

在上面的函数中,如果你添加一些模式来解构两个列表,那么可以避免第一个List.rev调用,直到它们都为空。

在F#中,有一个使用sequence expressions的尾递归方法,它位于continuation-passing style的一行:

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

我喜欢这种方法,因为它方便且接近您的原始配方。

答案 1 :(得分:2)

您可以在后续调用listMerge时累积构造结果,最后返回累积结果。我的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

答案 2 :(得分:1)

使用累加器的简单版本:

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)

我用200万个元素列表测试了这个,没有堆栈溢出,所以我有理由相信这是尾递归。

答案 3 :(得分:0)

我认为您必须重复使用F# PowerPack中找到的原始F#代码 实际上,你需要的是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"]

以下是我们的工作方式:

[<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
相应地,

g1g2是类型'State -> 'T1 list -> 'State'State -> 'T2 list -> 'State的谓词。他们告诉我们如何处理两个列表的剩余部分。请注意,其中有两个,因为通常情况'T1'T2是不同的类型。是的,它有点开销,但您可以轻松地将其降低到您的需求,牺牲普遍性。

用法:

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

答案 4 :(得分:0)

你应该使用模式匹配:

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

要获得尾调用,您可以使用累加器:

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

或继续传递风格:

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