[英]how to insert in the middle of a list, being tail-call friendly but without hurting performance?
所以我有這個功能似乎是非尾巴呼叫友好的,對吧?
let rec insertFooInProperPosition (foo: Foo) (bar: list<Foo>): list<Foo> =
match bar with
| [] -> [ foo ]
| head::tail ->
if (foo.Compare(head)) then
foo::bar
else
head::(insertFooInProperPosition foo tail)
然后我試着弄清楚如何使用累加器,這樣函數完成的最后一件事就是調用自己,我想出了這個:
let rec insertFooInProperPositionTailRec (foo: Foo) (headListAcc: list<Foo>) (bar: list<Foo>): list<Foo> =
match bar with
| [] -> List.concat [headListAcc;[ foo ]]
| head::tail ->
if (foo.Compare(head)) then
List.concat [headListAcc; foo::bar]
else
insertFooInProperPosition foo (List.concat [headListAcc;[head]]) tail
let rec insertFooInProperPosition (foo: Foo) (bar: list<Foo>): list<Foo> =
insertFooInProperPositionTailRec foo [] bar
但是,據我所知,List.concat的使用會使這個功能的效率降低,對吧? 那么我該如何正確地進行這種轉換呢?
如果需要使用遞歸,您的解決方案看起來沒問題。 但是,這個任務可以在沒有遞歸的情況下實現(並且更快一點)。
要連接兩個列表,應復制第一個列表,並將其最后一個元素指向第二個列表的第一個元素。 這是O(N),其中N是第一個列表的大小。 尾部的增長列表需要多個連接,從而產生每個N的遍歷列表,這使得復雜性呈二次方式(希望我在這里)。
更快的方法可能是找到插入索引,然后一次性復制所有項目,然后用新項目和列表的其余部分連接,而不是將項目添加到列表中的遞歸方法。 這只需要通過列表三次,所以O(N)。
let insertFooInProperPosition (foo : Foo) (bar : Foo list) : Foo list =
bar
|> List.tryFindIndex (fun v -> v.Compare foo)
|> function | None -> List.append bar [ foo ]
| Some i -> let before, after = List.splitAt i bar
List.concat [ before; [ foo ]; after ]
@AlexAtNet的解決方案看起來並不壞,但如果你還想要遞歸,你可以通過這種方式避免這么多的concat
調用:
let rec insertFooInProperPositionTailRec (foo: Foo)
(headListAcc: list<Foo>)
(bar: list<Foo>)
: list<Foo> =
match bar with
| [] -> List.rev (foo::headListAcc)
| head::tail ->
if (foo.Compare(head)) then
let newAcc = List.rev headListAcc
[ yield! newAcc
yield! foo::bar ]
else
let newAcc = head::headListAcc
insertFooInProperPositionTailRec foo newAcc tail
let rec insertFooInProperPosition (foo: Foo) (bar: list<Foo>): list<Foo> =
insertFooInProperPositionTailRec foo [] bar
不確定它是否比@ AlexAtNet更高效,嗯......
遺憾的是,你無法從頭到尾建立一個F#列表(除非你使用F#Core庫內部使用突變的函數)。 因此,最好的想法可能是從舊的列表中建立一個新列表,在我們前進時添加下一個元素,並在正確的位置插入foo
。 最后,新列表被反轉以獲得與舊列表相同的順序:
let insertFoo (foo : Foo) bar =
let rec loop acc = function
| [] -> prep (foo :: acc) []
| x :: xs ->
if foo.Compare x
then prep (x :: foo :: acc) xs
else loop (x :: acc) xs
and prep acc = function
| [] -> acc
| x :: xs -> prep (x :: acc) xs
loop [] bar |> List.rev
我猜@knocte更快的同等解決方案......
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.