如何使用F#获取没有第一个和最后一个项目的子列表?

时间:2015-03-17 13:23:49

标签: list f# sublist

我已经整理了整数值列表:

let ls = [1..4]

如何在没有第一个和最后一个元素的情况下获得子列表? (以最佳方式)

预期结果为[2; 3]

这是我到目前为止所做的,是的,它有效,但我认为这不是最好的方法。

[1..4] |> List.tail |> List.rev |> List.tail |> List.sort

3 个答案:

答案 0 :(得分:6)

响应你无辜措辞的资格赛来回答一段时间:"以最佳方式"

在什么方面最优?

  1. 性能? (最有可能)
  2. 性能还包括GC性能?
  3. 内存使用情况?
  4. 86 <?/ LI>
  5. 64 <?/ LI>

    等等......

    所以我决定测量问题的某些方面。

    我在不同的上下文中测量了不同的答案(也添加了一个非惯用的版本)。

    这里有一个我用来衡量的程序

    open System
    open System.Diagnostics
    open System.IO
    
    module so29100251 =
        // Daystate solution (OP)
        module Daystate =
            // Applied minor fixes to it
            let trim = function
                | [] | [_] | [_;_] -> []
                | ls -> ls |> List.tail |> List.rev |> List.tail |> List.rev
    
        // kaefer solution
        module kaefer =
            type 'a State = Zero | One | Other of 'a
    
            let skipFirstAndLast xss =
                let rec aux acc = function
                | _,            []    -> List.rev acc
                | Zero,         x::xs -> aux acc (One, xs)
                | One,          x::xs -> aux acc (Other x, xs)
                | (Other prev), x::xs -> aux (prev :: acc) (Other x, xs)
                aux [] (Zero, xss)
    
        // Petr solution
        module Petr =
            let rec trimImpl ls acc =
                match ls, acc with
                | [],      _   -> acc
                | h::[],   acc -> List.rev acc
                | h::n::t, []  -> trimImpl t [n]
                | h::t,    acc -> trimImpl t (h::acc)
    
            let trim ls = trimImpl ls []
    
        // NonIdiomatic solution
        module NonIdiomatic =
            let trim (hint : int) (ls : 'T list) =
                // trims last of rest
    
                // Can't ask for ls.Length as that is O(n)
                let ra = ResizeArray<_> (hint)
    
                // Can't use for x in list do as it relies on .GetEnumerator ()
                let mutable c = ls
                while not c.IsEmpty do
                    ra.Add c.Head
                    c <- c.Tail
    
                let count = ra.Count
    
                let mutable result = []
                for i in (count - 2)..(-1)..1 do
                    result <- ra.[i]::result
                result
    
    open so29100251
    
    type Time = MilliSeconds of int64
    
    type TestKind<'T> =
         | Functional               of 'T
         | MeasurePerformance       of int*int
    
    [<EntryPoint>]
    let main argv =
        let factor  = 10000000
    //    let maxHint = Int32.MaxValue
        let maxHint = 100
    
        let time (action : unit -> 'T) : 'T*Time =
            let sw = Stopwatch ()
    
            sw.Start ()
    
            let r = action ()
    
            sw.Stop ()
    
            r, MilliSeconds sw.ElapsedMilliseconds
    
        let adapt fn hint ls = fn ls
    
        let trimmers =
            [|
                "Daystate"      , adapt Daystate.trim
                "kaefer"        , adapt kaefer.skipFirstAndLast
                "Petr"          , adapt Petr.trim
                "NonIdiomatic"  , NonIdiomatic.trim
            |]
    
    
    #if DEBUG
        let functionalTestCases =
            [|
                Functional []               , "empty"       , []
                Functional []               , "singleton"   , [1]
                Functional []               , "duoton"      , [1;2]
                Functional [2]              , "triplet"     , [1;2;3]
                Functional [2;3]            , "quartet"     , [1;2;3;4]
            |]
    
        let performanceMeasurements = [||]
    #else
        let functionalTestCases = [||]
    
        let performanceMeasurements =
            [|
                "small"   , 10
                "big"     , 1000
                "bigger"  , 100000
    //            "huge"    , 10000000
            |] |> Array.map (fun (name, size) -> MeasurePerformance (size, (factor / size))  , name       , [for x in 1..size -> x])
    #endif
    
        let testCases =
            [|
                functionalTestCases
                performanceMeasurements
            |] |> Array.concat
    
    
        use tsv = File.CreateText ("result.tsv")
    
        tsv.WriteLine (sprintf "TRIMMER\tTESTCASE\tSIZE\tHINT\tRUNS\tMEMORY_BEFORE\tMEMORY_AFTER\tGC_TIME\tRUN_TIME")
    
        for trimName, trim in trimmers do
            for testKind, testCaseName, testCase in testCases do
                match testKind with
                | Functional expected ->
                    let actual = trim 0 testCase
                    if actual = expected then
                        printfn "SUCCESS: Functional test of %s trim on testcase %s successful" trimName testCaseName
                    else
                        printfn "FAILURE: Functional test of %s trim on testcase %s failed" trimName testCaseName
                | MeasurePerformance (size,testRuns) ->
    
                    let hint    = min size maxHint
    
                    let before  = GC.GetTotalMemory(true)
    
                    printfn "MEASURE: Running performance measurement on %s trim using testcase %s..." trimName testCaseName
    
                    let timeMe () =
                        for x in 1..testRuns do
                            ignore <| trim hint testCase
                    let _, MilliSeconds ms = time timeMe
    
                    let after   = GC.GetTotalMemory(false)
    
                    let timeGC () =
                        ignore <| GC.GetTotalMemory(true)
                    let _, MilliSeconds msGC = time timeMe
    
                    printfn "...%d ms (%d runs), %d (before) %d (after) %d ms (GC)" ms testRuns before after msGC
    
                    tsv.WriteLine (sprintf "%s\t%s\t%d\t%d\t%d\t%d\t%d\t%d\t%d" trimName testCaseName size hint testRuns before after msGC ms)
    
        0
    

    然后我测量了x64上的执行时间和GC时间以及允许的最大大小提示: (大小提示仅由非惯用版本使用)

    x64 maxhint

    允许x86和max size提示: x86 maxhint

    允许x64和最大100提示: x64 hint 100

    允许x86和最大100提示: x86 hint 100

    观察性能图表,我们可以注意到一些令人惊讶的事情:

    1. 所有变体都在迭代10000000次。人们会期望不同变体之间的执行时间没有差别,但确实如此。
    2. 整洁的旧x86总体上得分更高。我不会猜测为什么。
    3. OP的初始版本虽然看似浪费得分相当不错。它可能有助于List.rev非常优化(IIRC它只为F#开发人员做了一些安全的作弊)
    4. kaefer版本在纸上更好的解决方案似乎得分最差。我认为这是因为它分配了基于堆的额外State对象。 (这显然不应被解释为对kaefers技能的批评)
    5. 非惯用解决方案在良好的尺寸提示下得分良好,但不如我预期的那么好。可能是构建最终列表是大多数周期的成本。也可能是列表上的尾递归函数比while循环更有效,因为IIRC模式匹配比调用List.Tail / List.Head / List.IsEmpty
    6. 更有效。
    7. GC时间几乎和执行时间一样大。
    8. 我预计非惯用解决方案的GC时间明显低于其他时间。但是,ResizeArray&lt; _&gt;可能很快收集列表对象是不是
    9. 在x86 arch上,Petr解决方案与非惯用解决方案之间的性能差异可能无法保证额外的复杂性。
    10. 最后的一些想法:

      1. OP原创解决方案做得很好
      2. 垃圾收集需要时间
      3. 始终衡量......
      4. 希望它有点有趣

        编辑: GC性能测量数字不应过度解释为:&#34; GC可能很昂贵&#34;

        我后来在一个列表中从while循环更改为tail-recursion,这确实提高了性能,但不足以保证更新图表。

答案 1 :(得分:4)

这是其中一种方式:

let rec trim ls acc =
    match ls, acc with
    | [],      _   -> acc
    | h::[],   acc -> List.rev acc
    | h::n::t, []  -> trim t [n]  
    | h::t,    acc -> trim t (h::acc)

let reslt = trim ls []

答案 2 :(得分:2)

您并不需要标准库函数来实现这一目标,您只需要一种有效的方法。使用保存中间结果的累加器定义递归函数将成为可行的解决方案,即使列表必须在其终止时被反转。

我提供了一个自定义的Discriminated Union来跟踪状态,这是根据Option type的线条建模的,并附加一个案例。

type 'a State = Zero | One | Other of 'a

let skipFirstAndLast xss =
    let rec aux acc = function
    | _,            []    -> List.rev acc
    | Zero,         x::xs -> aux acc (One, xs)
    | One,          x::xs -> aux acc (Other x, xs)
    | (Other prev), x::xs -> aux (prev :: acc) (Other x, xs)
    aux [] (Zero, xss)

[1..4] |> skipFirstAndLast // val it : int list = [2; 3]