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