我读了this other post about a F# version of this algorithm。我发现它很优雅,并试图结合答案的一些想法。
虽然我对它进行了优化以减少检查(仅检查6左右的数字)并省去不必要的缓存,但仍然很慢。计算10,000 th 素数已经超过5分钟。使用命令式方法,我可以在更长的时间内测试所有31位整数。
所以我的问题是,如果我遗漏了使这一切变得如此缓慢的事情。例如,在another post中,有人推测LazyList
可能会使用锁定。有没有人有想法?
由于StackOverflow的规则说不要将新问题作为答案发布,我觉得我必须为此开始一个新主题。
以下是代码:
#r "FSharp.PowerPack.dll"
open Microsoft.FSharp.Collections
let squareLimit = System.Int32.MaxValue |> float32 |> sqrt |> int
let around6 = LazyList.unfold (fun (candidate, (plus, next)) ->
if candidate > System.Int32.MaxValue - plus then
None
else
Some(candidate, (candidate + plus, (next, plus)))
) (5, (2, 4))
let (|SeqCons|SeqNil|) s =
if Seq.isEmpty s then SeqNil
else SeqCons(Seq.head s, Seq.skip 1 s)
let rec lazyDifference l1 l2 =
if Seq.isEmpty l2 then l1 else
match l1, l2 with
| LazyList.Cons(x, xs), SeqCons(y, ys) ->
if x < y then
LazyList.consDelayed x (fun () -> lazyDifference xs l2)
elif x = y then
lazyDifference xs ys
else
lazyDifference l1 ys
| _ -> LazyList.empty
let lazyPrimes =
let rec loop = function
| LazyList.Cons(p, xs) as ll ->
if p > squareLimit then
ll
else
let increment = p <<< 1
let square = p * p
let remaining = lazyDifference xs {square..increment..System.Int32.MaxValue}
LazyList.consDelayed p (fun () -> loop remaining)
| _ -> LazyList.empty
loop (LazyList.cons 2 (LazyList.cons 3 around6))
答案 0 :(得分:2)
如果您在任何地方呼叫Seq.skip
,那么您有大约99%的机会拥有O(N ^ 2)算法。对于几乎所有涉及序列的优雅功能惰性Project Euler解决方案,您希望使用LazyList
,而不是Seq
。 (有关更多讨论,请参阅Juliet的评论链接。)
答案 1 :(得分:0)
即使您成功地驯服了奇怪的二次F#序列设计问题,仍然会有一些算法改进。您在(...((x-a)-b)-...)
方式工作。 x
或around6
越来越深,但它是最常产生的序列。 Transform将其(x-(a+b+...))
计划 - 甚至使用树结构来提高复杂度。 (对不起,该页面在Haskell中)。这实际上非常接近命令筛的复杂性,尽管仍比基线C ++代码慢得多。
将本地empirical orders of growth衡量为O(n^a) <--> a = log(t_2/t_1) / log(n_2/n_1)
({{1>}生成的n
),理想的n log(n) log(log(n))
转化为O(n^1.12) .. O(n^1.085)
行为n=10^5..10^7
范围。简单的C ++基线imperative code实现O(n^1.45 .. 1.18 .. 1.14)
而tree-merging code以及基于优先级队列的代码都表现出稳定的O(n^1.20)
行为,或多或少。当然C ++的速度要快〜 50 20..15 ,但这主要只是一个“常数因素”。 :)