使用函数编程有效地计算素数

时间:2012-03-19 07:48:45

标签: performance f# functional-programming

我通过浏览Project Euler并解决一些问题来熟悉F#。许多早期问题包括素数。环顾四周后,我提出了以下解决方案:

let primesL =
    let rec prim n sofar = 
        seq { if (sofar |> List.forall (fun i->n%i <>0L)) then
                  yield n
                  yield! prim (n+1L) (n::sofar)
              else
                  yield! prim (n+1L) sofar  }
    prim 2L []

这很好用,但后来我生成了所有素数高达2000000:

let smallPrimes = primesL |> Seq.takeWhile (fun n->n<=2000000)

这需要年龄。很明显,事情是在O(N ^ 2)或最差的情况下完成的。

我知道我可以写一个命令式版本并实现某种sieve,但我想坚持使用功能代码。如果我想要命令,我会留在C#。

我错过了什么?

5 个答案:

答案 0 :(得分:7)

我不是在这里写一个很长的答案,而是引用你Melissa O'Neill's great paper on the sieve of Eratosthenes

答案 1 :(得分:5)

您可能希望将您的方法与my variant of Problem Euler 10 solution

进行比较
let rec primes = 
    Seq.cache <| seq { yield 2; yield! Seq.unfold nextPrime 3 }
and nextPrime n =
    if isPrime n then Some(n, n + 2) else nextPrime(n + 2)
and isPrime n =
    if n >= 2 then
        primes 
        |> Seq.tryFind (fun x -> n % x = 0 || x * x > n)
        |> fun x -> x.Value * x.Value > n
    else false

纯功能,使用序列兑现,优化素性检查;它也会产生非常有用的isPrime n函数作为共同结果。

并应用于原始问题

let problem010 () =
    primes
    |> Seq.takeWhile ((>) 2000000)
    |> (Seq.map int64 >> Seq.sum)

它在体面的2.5秒中完成。这不是快速爆破,但足以在my other Project Euler solutions(27,35,37,50,58,69,70,77到目前为止)中使用这个primes序列。

至于您在解决方案中缺少的内容 - 从您的代码中我相信您正在为prim的每个内部调用构建一个全新的序列,即对于每个自然,而我的方法使用已发现素数的单个序列,并且在生成每个下一个素数时仅枚举其缓存实例。

答案 2 :(得分:1)

首先, O(n^2) - 请记住,每次迭代都使用List.forall

其次,如果你经常使用发生器,你应该缓存结果(这样每个素数只计算一次):

let primesL =
    let rec prim n sofar = 
        seq { if (sofar |> List.forall (fun i -> n % i <> 0UL)) then
                  yield n
                  yield! prim (n + 1UL) (n::sofar)
              else
                  yield! prim (n + 1UL) sofar }
    prim 2UL []
    |> Seq.cache

答案 3 :(得分:1)

你真正想要的是一个筛子 - 我在这之前写了一个非常快的F#筛:

F# parallelizing issue when calculating perfect numbers?

答案 4 :(得分:-1)

将“Miller–Rabin primality test”用于超过一些大素数