1 algorithm debugging f# sequence time-complexity
我在使用Seq.unfold
. 这是我可以想出的最小案例来重现这一点。
let idUnfolder sequence =
sequence
|> Seq.tryHead
|> Option.map (fun head -> (head, Seq.tail sequence))
let seqIdWithUnfold sequence =
Seq.unfold idUnfolder sequence
Run Code Online (Sandbox Code Playgroud)
该函数seqIdWithUnfold
返回给定序列本身。我希望结果序列像Seq.unfold
O(n)那样在线性时间内迭代,Seq.tryHead
并且Seq.tail
是 O(1)(如果我错了,请纠正我)。然而,据我所知,它具有三次复杂性。
我使用以下函数和一组n
值测试了执行时间。
let test n =
let start = System.DateTime.Now
Seq.init n id
|> seqIdWithUnfold
|> Seq.iter ignore
let duration = System.DateTime.Now - start
printfn "%f" duration.TotalSeconds
Run Code Online (Sandbox Code Playgroud)
是什么让这个操作变得复杂?
seq
几乎总是O(n)
。一个seq
又名IEnumerable<T>
基本上是:
type Enumerator<'a> = {
getNext : unit -> 'a option
}
type Seq<'a> = {
getEnumerator: unit -> Enumerator<'a>
}
Run Code Online (Sandbox Code Playgroud)
每次评估一个序列时,Enumerator
都会创建一个新的,它捕获枚举的状态。getNext
然后重复调用直到序列终止。
如果您seq
在任何时候将 a 替换为with,您可以亲眼看到这一点
source |> Seq.map(fun x -> printfn "Eval %A" x; x)
Run Code Online (Sandbox Code Playgroud)
让我们也显示调用getEnumerator
:
let sq =
seq {
let mutable ctr = 0
do printfn "Init _"
while true do
ctr <- ctr + 1
printfn "Yield %d" ctr
yield ctr
}
seqIdWithUnfold (sq |> Seq.take 3) |> Seq.toList |> printfn "%A"
Run Code Online (Sandbox Code Playgroud)
还有输出:
Init _
Yield 1
Init _
Yield 1
Yield 2
Init _
Yield 1
Yield 2
Yield 3
Init _
Yield 1
Yield 2
Yield 3
[1; 2; 3]
Run Code Online (Sandbox Code Playgroud)
这向您展示了经典n(n+1)/2
模式。
您可以看到复杂性将包含n + n 2 个项。
如果可以使用列表,您将得到O(n)
而不是O(n^2)
.
如果你真的想要O(1)
,使用数组。