在函数式编程上下文中使用不可变列表获取常量长度检索时间常量

时间:2011-09-03 17:08:34

标签: haskell f# functional-programming ocaml immutability

我目前面临的问题是必须根据给定列表的长度进行计算。由于我使用了相当大的列表,因此必须遍历列表中的所有元素以了解其大小是一个很大的性能损失。

该问题的建议方法是什么?

我想我总是可以携带一个大小值和列表,所以我事先知道它的大小,而不必在呼叫站点计算它,但这似乎是一个脆弱的方法。我还可以定义一个自己的列表类型,其中每个节点都具有列表大小的属性,但是我失去了我的编程语言库为标准列表提供的杠杆。

你们如何在日常生活中处理这个问题?

我目前正在使用F#。我知道我可以使用.NET的可变(数组)列表,这将解决问题。不过,我对纯粹不可改变的功能方法更感兴趣。

4 个答案:

答案 0 :(得分:6)

内置的F#列表类型没有任何长度缓存,也无法以某种巧妙的方式添加它,因此您需要定义自己的类型。我认为为现有的F#list类型编写包装器可能是最好的选择。

这样,您可以避免显式转换 - 当您包装列表时,它实际上不会复制它(如在svick的实现中),但包装器可以轻松地缓存Length属性:

open System.Collections

type LengthList<'T>(list:list<'T>) =
  let length = lazy list.Length
  member x.Length = length.Value
  member x.List = list
  interface IEnumerable with
    member x.GetEnumerator() = (list :> IEnumerable).GetEnumerator()
  interface seq<'T> with  //'
    member x.GetEnumerator() = (list :> seq<_>).GetEnumerator()

[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module LengthList =
  let ofList l = LengthList<_>(l)
  let ofSeq s = LengthList<_>(List.ofSeq s)
  let toList (l:LengthList<_>) = l.List
  let length (l:LengthList<_>) = l.Length

使用包装器的最佳方法是使用LengthList.ofList从标准F#列表创建LengthList并使用LengthList.toList(或仅List)属性在使用标准List模块中的任何函数之前。

但是,这取决于代码的复杂程度 - 如果您只需要几个地方的长度,那么单独保留它并使用元组list<'T> * int可能会更容易。

答案 1 :(得分:5)

  

你们如何在日常生活中处理这个问题?

我们没有,因为这不是日常生活中的问题。这听起来像是一个问题,可能在有限的领域。

如果您最近创建了列表,那么您可能已经完成了O(N)工作,因此遍历列表以获取其长度可能不是什么大问题。

如果您制作的是一些非常大的列表,那么这些列表并没有“更改”(显然从未改变,但我的意思是更改对域/算法中使用的列表头部的引用集),那么它可能会有意义的只是在引用到列表头*长度元组的一侧有一个字典,并在询问长度时查阅字典(做真正的工作在需要时走它们,但缓存结果以便将来询问有关相同的清单)。

最后,如果你真的在处理一些需要不断更新游戏中的列表并不断查询长度的算法,那么就创建一个类似列表的数据类型(是的,你还需要编写map /过滤器和任何其他)。

(一般来说,我认为通常最好在99.99%的时间内使用内置数据结构。在开发算法或代码需要非常高的0.01%的时间内优化,然后几乎总是你需要放弃内置数据结构(这对于大多数情况来说已经足够好了)并使用自定义数据结构来解决您正在处理的确切问题。查看维基百科或Okasaki的“'纯功能数据结构“在这种情况下的想法和启发。但很少去那种情况。”

答案 2 :(得分:3)

我不明白为什么围绕长度是一种脆弱的方法。试试这样的事情(Haskell):

data NList a = NList Int [a]

nNil :: NList [a]
nNil = NList 0 []

nCons :: a -> NList a -> NList a
nCons x (NList n xs) = NList (n+1) (x:xs)

nHead :: NList a -> a
nHead (NList _ (x:_)) = x

nTail :: NList a -> NList a
nTail (NList n (_:xs)) = NList (n-1) xs

convert :: [a] -> NList a
convert xs = NList (length xs) xs

等等。如果这是在库或模块中,您可以通过不导出构造函数NList来使其安全(我认为)。

也可以强制GHC进行记忆length,但我不确定如何或何时。

答案 3 :(得分:1)

在F#中,大多数List函数具有等效的Seq函数。这意味着,您可以实现自己的不可变链表,其中包含每个节点的长度。像这样:

type MyList<'T>(item : Option<'T * MyList<'T>>) =

    let length =
        match item with
        | None -> 0
        | Some (_, tail) -> tail.Length + 1

    member this.Length = length

    member private this.sequence =
        match item with
        | None -> Seq.empty
        | Some (x, tail) ->
            seq {
                yield x
                yield! tail.sequence
            }

    interface seq<'T> with
        member this.GetEnumerator() =
            (this.sequence).GetEnumerator()
        member this.GetEnumerator() =
            (this.sequence :> System.Collections.IEnumerable).GetEnumerator()

module MyList =
    let rec ofList list =
        match list with
        | [] -> MyList None
        | head::tail -> MyList(Some (head, ofList tail))