我想知道当序列转换为数组然后再将其视为序列时,我是否会得到特殊处理。
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#中按顺序记住它们的内部结构是一个数组吗?
我问这个问题,因为在昂贵的序列上,你可能需要多次长度,并且一次评估它将是有益的。我可以创建一个记住长度的特定序列类型,或者我可以使用已经存在的魔法。
答案 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>
的数组类型,列表和实例进行动态类型检查。