最初に、素数のオイラーのふるいは「エラトステネスのふるいの改良版」ではないということを言わなければなりません。あらゆる意味でのパフォーマンスは、エラトステネスのふるいのどのバージョンよりもはるかに悪いからです。 素数アルゴリズムに関する Haskell Wiki -オイラー
次に、LazyList を使用した @cfem のコードは、上記のリンクに従って奇数のみをふるい分けするというわずかな改善が欠けていますが、あなたが提供したオイラーのふるいのバージョンの冗長ではあるが忠実な翻訳であると言わなければなりません。
オイラーふるいの実装には実際には何の意味もないことに注意する必要があります。これは、TDO (Trial Division Optimized) によって素数を見つけるよりも複雑で遅いため、見つかった素数による除算のみを の平方根まで行うためです。Haskell Wiki on Prime Number algorithm - TDOに従って、素数についてテストされた候補数 。
このTrial Division Optimized (TDO) ふるいは、LazyList (FSharp.PowerPack.dll への参照) を使用して F# に実装できます。
let primesTDO() =
let rec oddprimes =
let rec oddprimesFrom n =
if oddprimes |> Seq.takeWhile (fun p -> p * p <= n) |> Seq.forall (fun p -> (n % p) <> 0)
then LazyList.consDelayed n (fun() -> oddprimesFrom (n + 2))
else oddprimesFrom (n + 2)
LazyList.consDelayed 3 (fun() -> oddprimesFrom 5)
LazyList.consDelayed 2 (fun () -> oddprimes)
以下と同じ形式のシーケンスを使用して実装できます。
let primesTDOS() =
let rec oddprimes =
let rec oddprimesFrom n =
if oddprimes |> Seq.takeWhile (fun p -> p * p <= n) |> Seq.forall (fun p -> (n % p) <> 0)
then seq { yield n; yield! oddprimesFrom (n + 2) }
else oddprimesFrom (n + 2)
seq { yield 3; yield! (oddprimesFrom 5) } |> Seq.cache
seq { yield 2; yield! oddprimes }
LazyList はキャッシュされたシーケンスに基づいているため、シーケンス バージョンは LazyList バージョンよりもわずかに高速です。どちらも、これまでに見つかった素数のキャッシュされたコピーを表す内部オブジェクトを使用します。LazyList の場合は自動的にキャッシュされ、シーケンスの場合は Seq.cache によってキャッシュされます。どちらも最初の 100,000 個の素数を約 2 秒で見つけることができます。
これで、オイラーふるいは奇数ふるいの最適化を行うことができ、次のように LazyList を使用して表現できます。入力リストのパラメーターが無限であり、比較一致が単純化されていることがわかっているため、1 つの一致ケースが削除され、演算子も追加されています。コードを読みやすくするための '^':
let primesE = //uses LazyList's from referenced FSharp.PowerPack.dll version 4.0.0.1
let inline (^) a ll = LazyList.cons a (LazyList.delayed ll) //a consd function for readability
let rec eulers xs =
//subtracts ys from xs, where xs and ys are both sorted(!) infinite lazy lists
let rec (-) xs ys =
let x,xs',ys' = LazyList.head xs,LazyList.tail xs,LazyList.tail ys
match compare x ( LazyList.head ys) with
| 0 -> xs' - ys' // x == y
| 1 -> xs - ys' // x > y
| _ -> x^(fun()->(xs' - ys)) // must be x < y
let p = LazyList.head xs
p^(fun() -> (((LazyList.tail xs) - (LazyList.map ((*) p) xs)) |> eulers))
let rec everyothernumFrom x = x^(fun() -> (everyothernumFrom (x + 2)))
2^(fun() -> ((everyothernumFrom 3) |> eulers))
ただし、1899 番目の素数 (16381) を計算する時間は、primesTDO と primesTDOS でそれぞれ約 0.2 秒と 0.16 秒であることに注意する必要があります。この小さな範囲でも 10 倍の時間です。ひどいパフォーマンスに加えて、primeE は 3000 までの素数を計算することさえできません。これは、見つかった素数の増加に伴い、遅延実行関数の数が急速に増加しているため、メモリ使用率がさらに悪化するためです。
LazyList は値であり、以前に見つかった要素の記憶が組み込まれているため、記述されたコードの繰り返しのタイミングに注意する必要があることに注意してください。タイミングのために、PrimeE を PrimeE() のように関数にして、作業が毎回最初から開始されるようにする方がよい場合があります。
要約すると、F# を含む任意の言語で実装されたオイラーふるいは、興味深い知的な演習にすぎず、合理的に最適化された他のほぼすべてのふるいよりもはるかに遅く、メモリを大量に消費するため、実用的ではありません。