快速获得Eratosthenes的功能筛选

时间:2011-06-24 15:22:41

标签: performance f# lazy-evaluation primes sieve-of-eratosthenes

我读了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))

2 个答案:

答案 0 :(得分:2)

如果您在任何地方呼叫Seq.skip,那么您有大约99%的机会拥有O(N ^ 2)算法。对于几乎所有涉及序列的优雅功能惰性Project Euler解决方案,您希望使用LazyList,而不是Seq。 (有关更多讨论,请参阅Juliet的评论链接。)

答案 1 :(得分:0)

即使您成功地驯服了奇怪的二次F#序列设计问题,仍然会有一些算法改进。您在(...((x-a)-b)-...)方式工作。 xaround6越来越深,但它是最常产生的序列。 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 ,但这主要只是一个“常数因素”。 :)