簡體   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