【发布时间】:2022-01-17 16:15:45
【问题描述】:
我正在实现两个版本的 Eratosthenes's Sieve,第一个是必要的:
let primesUntilArray n =
let isqrt x =
let mutable root = 0UL
let mutable p = 1UL <<< 31
while p > 0UL do
while x < (root + p) * (root + p) do
p <- p >>> 1
root <- root + p
p <- p >>> 1
root
let isPrime = Array.create (int <| n + 1) true
let bound = int <| isqrt (uint64 n)
let mutable i = 2
while i <= bound do
if isPrime.[i] then
let mutable j = i * i
while j <= n do
isPrime.[j] <- false
j <- j + i
i <- i + 1
let mutable primes = []
let mutable i = 2
while i <= n do
if isPrime.[i] then
primes <- i :: primes
i <- i + 1
List.rev primes
而第二个是函数式的(使用 F# 的序列):
let primesUntilSequence n =
let rec pickPrimes s =
let sieveWith p =
Seq.filter (fun n -> n < p * p || n % p <> 0)
let p = Seq.head s
seq {
yield p
yield! pickPrimes (sieveWith p (Seq.tail s))
}
Seq.initInfinite (fun i -> i + 2)
|> pickPrimes
|> Seq.takeWhile (fun p -> p <= n)
|> Seq.toList
两者的时间复杂度都在O(n log log n)左右,但是使用sequence的版本性能很差,例如
> primesUntilArray 9000 |> List.length |> printfn "%d";;
1117
Real: 00:00:00.004, CPU: 00:00:00.000, GC Gen0: 1, Gen1: 0, Gen2: 0
val it: unit = ()
> primesUntilSequence 9000 |> List.length |> printfn "%d";;
1117
Real: 00:00:15.388, CPU: 00:00:15.375, GC Gen0: 592, Gen1: 64, Gen2: 0
val it: unit = ()
这意味着生成素数要慢大约 4000 倍,直到 9000。如何提高第二个的性能?
谢谢大家。 以下是@Tomas Petricek 解决方案的略微修改版本
let primesUntilList n =
let rec pickPrimes ps xs =
let sieveWith p =
List.filter (fun n -> n < p * p || n % p <> 0)
match xs with
| p :: xs' ->
if p * p > n then
(List.rev xs) @ ps
else
pickPrimes (p :: ps) (sieveWith p xs')
| _ -> ps
List.rev <| pickPrimes [] [ 2..n ]
这个基于列表的版本仍然比使用数组的效率低大约两倍,但比我最初的基于序列的版本要好得多。
【问题讨论】: