Haskell → F #: Turner Sieve

Я читал разные алгоритмы просеивания, когда наткнулся на улучшенную версию сита Эратосфена под названием Euler Sieve. Согласно Wikipedia, в Haskell существует реализация немного отличающейся версии идеи (называемой Turner Sieve).

Теперь я пытаюсь понять, что именно дает этот фрагмент кода, и я думаю, что у меня это есть, но теперь я хотел перевести код в F # и понятия не имею, с чего начать. Моя главная проблема заключается в том, что, похоже, не существует функции "вычесть" две последовательности.

Здесь код:

import Data.OrdList (minus)

primes = euler [2..]
euler (p : xs) = p : euler (xs `minus` map (*p) (p : xs))

Как это будет реализовано в F #? Возможно ли это?

Ответ 1

Если вы хотите рассчитать такие вещи, как слияния/различия в бесконечных списках, например, Haskell, тип LazyList (найденный внутри блока питания F #) возникает из виду.

Он делает для очень многословного кода, как и для перевода ниже:

#r "FSharp.PowerPack.dll"

//A lazy stream of numbers, starting from x
let rec numsFrom x = LazyList.consDelayed x (fun () -> numsFrom (x+1))

//subtracts L2 from L1, where L1 and L2 are both sorted(!) lazy lists
let rec lazyDiff L1 L2 =
    match L1,L2 with
        | LazyList.Cons(x1,xs1),LazyList.Cons(x2,xs2) when x1<x2 ->
            LazyList.consDelayed x1 (fun () -> lazyDiff xs1 L2)
        | LazyList.Cons(x1,xs1),LazyList.Cons(x2,xs2) when x1=x2 ->
            lazyDiff xs1 L2
        | LazyList.Cons(x1,xs1),LazyList.Cons(x2,xs2) when x1>x2 ->
            lazyDiff L1 xs2
        | _ -> failwith "Should not occur with infinite lists!"

let rec euler = function
    | LazyList.Cons(p,xs) as LL ->
        let remaining = lazyDiff xs (LazyList.map ((*) p) LL)
        LazyList.consDelayed p (fun () -> euler remaining)
    | _ -> failwith "Should be unpossible with infinite lists!"

let primes = euler (numsFrom 2)

с

> primes |> Seq.take 15 |> Seq.toList;;
val it : int list = [2; 3; 5; 7; 11; 13; 17; 19; 23; 29; 31; 37; 41; 43; 47]

Обратите внимание, что я добавил два предложения failwith, чтобы компилятор не жаловался на неполное соответствие, даже если мы знаем, что все списки в вычислении (лениво) бесконечны.

Ответ 2

Вы можете сделать это с помощью seq. И поскольку вы получили минус, euler сам такой же, как в Haskell.

let rec minus xs ys =
  seq {
    match Seq.isEmpty xs, Seq.isEmpty ys with
    | true,_ | _,true -> yield! xs
    | _ ->
       let x,y = Seq.head xs, Seq.head ys
       let xs',ys' = Seq.skip 1 xs, Seq.skip 1 ys
       match compare x y with
       | 0 -> yield! minus xs' ys'
       | 1 -> yield! minus xs ys'
       | _ -> yield x; yield! minus xs' ys
  }

let rec euler s =
  seq {
    let p = Seq.head s
    yield p
    yield! minus (Seq.skip 1 s) (Seq.map ((*) p) s) |> euler
  }

let primes = Seq.initInfinite ((+) 2) |> euler

Ответ 3

С помощью последовательностей вы получаете гораздо более конкурентную способность, сохраняя последовательность:

let rec euler s =
    seq {
        let s = Seq.cache s
        let p = Seq.head s
        yield p
        yield! minus (Seq.skip 1 s) (Seq.map ((*) p) s) |> euler
    }

Ответ 4

Во-первых, следует сказать, что сито Эйлера для простых чисел не является "улучшенной версией Сита Эратосфена", поскольку его производительность во всех смыслах намного хуже, чем любая версия Сита Эратосфена: Haskell Wiki по алгоритмам Prime Number - Эйлер

Далее следует сказать, что код @cfem с использованием LazyList является верным, хотя и подробным переводом версии сита Эйлера, которую вы дали, хотя ему не хватает небольшого улучшения только просеивания нечетных чисел в соответствии с приведенной выше ссылкой.

Следует отметить, что на самом деле нет никакого смысла в реализации сита Эйлера, поскольку он более сложный и медленный, чем поиск простых чисел с помощью Trial Division Optimized (TDO) только для того, чтобы делать деления только найденными штрихами до квадрата корень из числа кандидатов, проверенных на первичный: Haskell Wiki по алгоритмам Prime Number - TDO.

Это сильное Trial Division Optimized (TDO) сито может быть реализовано в F # с использованием LazyList (со ссылкой на 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 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 для ужасной производительности для Эйлера сита в десять раз больше времени даже для этого небольшого диапазона. В дополнение к ужасной производительности, простой вариант не может даже вычислить простые числа до 3000 из-за еще более худшего использования памяти, поскольку он записывает быстро растущее число отложенных функций выполнения с увеличением найденных простых чисел.

Обратите внимание, что нужно соблюдать осторожность при повторном времени кода, как написано, поскольку LazyList является значением и имеет встроенное запоминание ранее найденных элементов, таким образом, второе идентичное сканирование приблизится к нулевому времени; для целей синхронизации было бы лучше сделать PrimeE функцией как в PrimeE(), поэтому работа начинается с начала каждый раз.

Таким образом, сито Euler, реализованное на любом языке, включая F #, представляет собой интересное интеллектуальное упражнение и не имеет практического применения, поскольку оно намного медленнее, а память свиней намного хуже, чем почти любое другое разумно оптимизированное сито.