如果我将一个序列转换为一个数组并将其视为一个序列,那么我的长度是否为O(1)?

时间:2016-09-25 16:02:55

标签: arrays f# seq

我想知道当序列转换为数组然后再将其视为序列时,我是否会得到特殊处理。

let sq = seq { for i in 0 .. 10 do yield i }
let arr = Seq.toArray sq
let len = Array.length arr      // O(1)
let sq2 = arr |> Seq.ofArray

// from converted seq
let len2 = Seq.length sq2       // O(n)???

// or direct:
let len2 = Seq.length arr       // O(n)???

出于同样的原因,F#足够智能Seq.toArray arr来创建数组的副本,不管它(不创建副本),还是使用枚举器迭代每个项目?< / p>

换句话说,在F#中按顺序记住它们的内部结构是一个数组吗?

我问这个问题,因为在昂贵的序列上,你可能需要多次长度,并且一次评估它将是有益的。我可以创建一个记住长度的特定序列类型,或者我可以使用已经存在的魔法。

2 个答案:

答案 0 :(得分:4)

如果序列实际上是一个数组类型,那么它将被简单地转换回一个数组以确定Seq.length中的数组。您可以在length函数here

的实现中看到这一点
[<CompiledName("Length")>]
let length (source : seq<'T>)    = 
    checkNonNull "source" source
    match source with 
    | :? ('T[]) as a -> a.Length
    | :? ('T list) as a -> a.Length
    | :? ICollection<'T> as a -> a.Count
    | _ -> 
        use e = source.GetEnumerator() 
        let mutable state = 0 
        while e.MoveNext() do
            state <-  state + 1;
        state

如果你把它放在FSI中,你会看到这种行为:

let arr = [|1..40000000|];;

使用Array.length

Array.length arr;;
Real: 00:00:00.000, CPU: 00:00:00.015, GC gen0: 0, gen1: 0, gen2: 0
val it : int = 40000000

使用Seq.length

Seq.length arr;;
Real: 00:00:00.000, CPU: 00:00:00.000, GC gen0: 0, gen1: 0, gen2: 0
val it : int = 40000000

如果使用Seq.ofArray,则特意隐藏基础类型信息,创建一个按元素逐步遍历数组元素的新枚举器。

这可能是一种有用的行为,因为它可以防止您的API的使用者悄悄地将seq<'T>强制转换回'T[],从而允许所述消费者改变您(API设计者)预期会发生的事情。暴露出不可变的观点。

此信息隐藏的缺点是您无法转换回数组,因此枚举变得非常慢:

Seq.length <| Seq.ofArray arr;;
Real: 00:00:00.148, CPU: 00:00:00.140, GC gen0: 0, gen1: 0, gen2: 0
val it : int = 40000000

Seq.ofArray使用的mkSeq function只会从IEnumerable创建一个匿名的ArrayEnumerator

let mkSeq f = 
    { new IEnumerable<'U> with 
          member x.GetEnumerator() = f()
      interface IEnumerable with 
          member x.GetEnumerator() = (f() :> IEnumerator) }

答案 1 :(得分:2)

Seq.ofArray会返回ArrayEnumerator,只会IEnumerator<T>,因此调用Seq.length将需要枚举整个序列以获取长度。

直接在数组上调用Seq.length将使用基础Length属性,因为它对ICollection<T>的数组类型,列表和实例进行动态类型检查。