简体   繁体   English

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

[英]how to insert in the middle of a list, being tail-call friendly but without hurting performance?

So I have this function which seems to be non-tail-call friendly, right? 所以我有这个功能似乎是非尾巴呼叫友好的,对吧?

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)

Then I try to figure out how to use an accumulator so that the last thing done by the function is call itself, and I come up with this: 然后我试着弄清楚如何使用累加器,这样函数完成的最后一件事就是调用自己,我想出了这个:

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

However, as far as I understand, the usage of List.concat would make this function much less efficient, right? 但是,据我所知,List.concat的使用会使这个功能的效率降低,对吧? So then how would I go and do this conversion properly? 那么我该如何正确地进行这种转换呢?

Your solution looks ok if using recursion is required. 如果需要使用递归,您的解决方案看起来没问题。 However, the this task can be achieved without recursion (and a bit faster). 但是,这个任务可以在没有递归的情况下实现(并且更快一点)。

To concatenate two lists the first list should be copied and its last element should point to the first element of the second list. 要连接两个列表,应复制第一个列表,并将其最后一个元素指向第二个列表的第一个元素。 This is O(N) where N is the size of the first list. 这是O(N),其中N是第一个列表的大小。 Growing list at the tail requires multiple concatenations, resulting traversing list for each N, which makes the complexity quadratic (hope I right here). 尾部的增长列表需要多个连接,从而产生每个N的遍历列表,这使得复杂性呈二次方式(希望我在这里)。

Instead of recursive approach that adds items to the list, the faster approach would be probably to find the insertion index and then copy all the items before it in one go and then concat it with new item and the rest of the list. 更快的方法可能是找到插入索引,然后一次性复制所有项目,然后用新项目和列表的其余部分连接,而不是将项目添加到列表中的递归方法。 This requires only three passes through the list so O(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's solution doesn't look bad, but if you still want recursive, you can avoid so much concat calls this way: @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

Not sure if it's more performant than @AlexAtNet's, mmm... 不确定它是否比@ AlexAtNet更高效,嗯......

Unfortunately you cannot build up an F# list from head to tail (unless you use functions internal to the F# Core library that use mutation under the hood). 遗憾的是,你无法从头到尾建立一个F#列表(除非你使用F#Core库内部使用突变的函数)。 Therefore the best idea is probably to build up a new list from the old one, prepending the next elements as we go, and inserting foo at the right point. 因此,最好的想法可能是从旧的列表中建立一个新列表,在我们前进时添加下一个元素,并在正确的位置插入foo At the end, the new list is reversed to obtain the same order as the old list: 最后,新列表被反转以获得与旧列表相同的顺序:

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

I guess @knocte was quicker with an equivalent solution… 我猜@knocte更快的同等解决方案......

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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