Во-первых, следует сказать, что сито Эйлера для простых чисел не является «улучшенной версией Эратосфенского сита», поскольку его производительность во всех смыслах намного хуже, чем у любой другой версии сито Эратосфена: Haskell Вики об алгоритмах простых чисел - Эйлер
Далее следует сказать, что код @ cfem, использующий LazyList's, является точным, хотя и подробным переводом версии сита Эйлера, которую вы дали, хотя в нем отсутствует незначительное улучшение только просеивания нечетных чисел по ссылке выше.
Следует отметить, что в действительности нет смысла внедрять сито Эйлера, поскольку оно сложнее и медленнее, чем поиск простых чисел с помощью Trial Division Optimized (TDO), в отношении выполнения делений только по найденным простым числам вплоть до квадрата. корень числа кандидатов, проверенного на простое число согласно: Haskell Wiki по алгоритмам простых чисел - TDO .
Это сито Trial Division Optimized (TDO) может быть реализовано в F # с использованием LazyList's (со ссылкой на FSharp.PowerPack.dll) как:
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 простых чисел примерно за две секунды.
Теперь сито Эйлера может иметь оптимизацию просеивания нечетных чисел и может быть выражено с использованием LazyList следующим образом, при этом исключен один случай совпадения, поскольку известно, что параметры входного списка бесконечны, а сопоставление сравнения упрощено. Также я добавил оператор '^', чтобы сделать код более читабельным:
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) составляет примерно 0,2 и 0,16 секунды для primesTDO и primesTDOS, соответственно, в то время как это примерно 2,5 секунды, используя это primesE для ужасной производительности для Эйлера. сито более чем в десять раз, даже для этого небольшого диапазона. В дополнение к ужасной производительности, PrimeE не может даже вычислить простые числа до 3000 из-за еще худшего использования памяти, поскольку записывает быстро растущее число функций отложенного выполнения с увеличением найденных простых чисел.
Обратите внимание, что нужно соблюдать осторожность при повторном хронировании кода, как написано, поскольку LazyList является значением и имеет встроенное запоминание ранее найденных элементов, поэтому второе идентичное сканирование займет время, близкое к нулю; в целях синхронизации может быть лучше сделать PrimeE функцией, как в PrimeE (), чтобы работа каждый раз начиналась с начала.
Таким образом, сито Эйлера, реализованное на любом языке, включая F #, является лишь интересным интеллектуальным упражнением и не имеет практического применения, поскольку оно намного медленнее и загружает память намного хуже, чем почти все другие разумно оптимизированные сита.