繁体   English   中英

如何在列表中间插入,尾部调用友好但不会影响性能?

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

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