我通过浏览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#。
我错过了什么?
答案 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#筛:
答案 4 :(得分:-1)
将“Miller–Rabin primality test”用于超过一些大素数