[英]How to convert this function to use tail call
遞歸函數:
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 []
正如@Ramon建議的那樣,您應該使用模式匹配來提高可讀性:
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#中,有一個使用序列表達式的尾遞歸方法,它沿着連續傳遞樣式 :
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
我喜歡這種方法,因為它方便且接近您的原始配方。
您可以在后續調用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
使用累加器的簡單版本:
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萬個元素列表測試了這個,沒有堆棧溢出,所以我有理由相信這是尾遞歸。
我認為你必須重用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
g1
和g2
是類型'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
你應該使用模式匹配:
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
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.