Решето Эратосфена в Хаскелле

Я решаю некоторые классические задачи на Haskell, чтобы развить свои функциональные навыки, и у меня есть проблема с реализацией оптимизации, предложенной на этом "Практика программирования" сайт:

У меня есть три решения этой проблемы, и третье слишком медленное по сравнению со вторым решением. Может ли кто-нибудь предложить некоторые улучшения моего кода?

Мои реализации:

-- primeira implementação
primes n
    | n < 2 = []
    | n == 2 = [2]
    | n `mod` 2 == 0 = primes'
    | otherwise = if (find (\x -> n `mod` x == 0) primes') == Nothing then
                      n:primes'
                  else
                      primes'
    where primes' = primes (n - 1)

-- segunda implementação
primes' :: Integer -> [Integer]
primes' n = sieve $ 2 : [3,5..n]
    where sieve :: [Integer] -> [Integer]
          sieve [] = []
          sieve l@(x:xs)
              | x*x >= n = l
              | otherwise = x : sieve list'
              where list' = filter (\y -> y `mod` x /= 0) xs

-- terceira implementação
primes'' :: Integer -> [Integer]
primes'' n = 2 : sieve 3 [3,5..n]
    where sieve :: Integer -> [Integer] -> [Integer]
          sieve _ [] = []
          sieve m l@(x:xs)
              | m*m >= n = l
              | x < m*m = x : sieve m xs
              | otherwise = sieve (m + 2) list'
              where list'= filter (\y -> y `mod` m /= 0) l

person Max Reinhold Jahnke    schedule 04.10.2010    source источник
comment
1) Ваш английский в порядке. 2) Мне очень нравится, что вы прикрепили исполняемый код - так мало вопросов в последнее время. 3) Вы также можете включить свою команду компиляции - некоторые люди забывают -O2 (и когда-то существовала экспериментальная -O3, которая замедляла работу многих программ, но новички об этом не знали).   -  person Thomas M. DuBuisson    schedule 04.10.2010
comment
Вас может заинтересовать статья Подлинное сито Эратосфена . Он охватывает несколько различных реализаций (включая поддельное сито) и рассказывает об основных характеристиках производительности, а также предлагает некоторые способы ускорения алгоритма.   -  person    schedule 04.10.2010
comment
Одно из важных сообщений из статьи «Подлинное решето Эратосфена» заключается в том, что, хотя коды, подобные этим трем, часто предоставляются как «реализации решета Эратосфена», их производительность ближе к алгоритму пробного деления (медленное ), чем к решету Эратосфена (быстрому).   -  person Tsuyoshi Ito    schedule 04.10.2010
comment
Как подразумевали другие, ваши три кода - это не Решето Эратосфена, а скорее варианты Пробного деления (оператор по модулю mod вызывает деление). Что касается того, почему третий код такой медленный, то это двойная отбраковка по всем нечетным числам, а не только по ранее найденным простым числам, как во втором коде. Варианты реальных реализаций Sieve of Eratothenes см. на странице Haskell Primes, где решения основаны на STUArray намного быстрее, хотя и не является чисто функциональным (изменение состояния массива внутри монады ST).   -  person GordonBGood    schedule 04.02.2014
comment
наверняка вы имели в виду, что первый вариант слишком медленный, поскольку он квадратичен (или хуже) в k простых числах. второй - своего рода неудачное сито Тернера (т.е. сито с пробным делением) по простым числам (что составляет около ~ k^1,5 плюс-минус log множитель), и третья - то же самое, но с нечетными числами (что должно сделать ее хуже второй еще на log коэффициент). Это также можно оценить эмпирически.   -  person Will Ness    schedule 25.12.2018


Ответы (3)


Мне кажется, что проблема с вашей третьей версией заключается в том, как вы выбираете следующий элемент для просеивания. Вы без разбора увеличиваете на 2. Проблема в том, что вы затем отсеиваете ненужные числа. например, в этой версии вы в конечном итоге передадите 9 как m, и вы собираетесь сделать дополнительную рекурсию для фильтрации по 9, даже если его даже нет в списке, и поэтому вы никогда не должны были выбирать его в первое место (поскольку его бы убрали в самом первом фильтре на 3)

Несмотря на то, что вторая версия не начинает фильтрацию дальше квадрата числа, которое она отсеивает, она никогда не выбирает ненужное значение отсеивания.

Другими словами, я думаю, что в конечном итоге вы будете просеивать каждое нечетное число от 3 до n. Вместо этого вы должны просеивать каждое нечетное число, которое еще не было удалено предыдущим проходом.

Я думаю, чтобы правильно реализовать оптимизацию запуска сита в квадрате текущего значения просеивания, вы должны сохранить начало списка при просеивании сзади, где задняя часть содержит элементы> = квадрат значения просеивания. Я думаю, что это заставит вас использовать конкатенации, и я не уверен, что оптимизация достаточно хороша, чтобы компенсировать накладные расходы, вызванные использованием ++.

person mayahustle    schedule 04.10.2010
comment
отсеивание каждого нечетного числа, которое еще не было удалено предыдущим проходом IOW каждое нечетное простое число. таким образом, чтобы исправить primes'', нам нужно передать результат tail $ primes'' (ceiling . sqrt . fromIntegral $ n) в качестве первого аргумента в sieve и продвигаться по нему, повторяя tails вместо (+ 2). тогда он должен стать быстрее, чем primes', потому что, в отличие от него, primes'' правильно откладывает фильтрацию до квадрата числа для фильтрации. - person Will Ness; 25.12.2018

Прежде всего, mod работает медленно, поэтому используйте rem в ситуациях, когда это не имеет значения (в основном, когда вы не имеете дело с негативами). Во-вторых, используйте Criterion, чтобы показать (себе), что работает быстрее, а какие изменения на самом деле являются оптимизацией. Я знаю, что не даю полный ответ на ваш вопрос с этим, но это хорошее место для вас (и других потенциальных ответчиков), чтобы начать, так что вот некоторый код:

import List
import Criterion.Main

main = do
  str <- getLine
  let run f = length . f
      input = read str :: Integer
  defaultMain   [ bench "primes" (nf (run primes) input)
                , bench "primes'" (nf (run primes') input)
                , bench "primes''" (nf (run primes'') input)
                , bench "primesTMD" (nf (run primesTMD) input)
                , bench "primes'TMD" (nf (run primes'TMD) input)
                , bench "primes''TMD" (nf (run primes''TMD) input)
                ]
  putStrLn . show . length . primes'' $ (read str :: Integer)

-- primeira implementação
primes n
    | n < 2 = []
    | n == 2 = [2]
    | n `mod` 2 == 0 = primes'
    | otherwise = if (find (\x -> n `mod` x == 0) primes') == Nothing then
                      n:primes'
                  else
                      primes'
    where primes' = primes (n - 1)

primesTMD n
    | n < 2 = []
    | n == 2 = [2]
    | n `mod` 2 == 0 = primes'
    | otherwise = if (find (\x -> n `rem` x == 0) primes') == Nothing then
                      n:primes'
                  else
                      primes'
    where primes' = primesTMD (n - 1)

-- segunda implementação
primes' :: Integer -> [Integer]
primes' n = sieve $ 2 : [3,5..n]
    where sieve :: [Integer] -> [Integer]
          sieve [] = []
          sieve l@(x:xs)
              | x*x >= n = l
              | otherwise = x : sieve list'
              where list' = filter (\y -> y `mod` x /= 0) xs

primes'TMD :: Integer -> [Integer]
primes'TMD n = sieve $ 2 : [3,5..n]
    where sieve :: [Integer] -> [Integer]
          sieve [] = []
          sieve l@(x:xs)
              | x*x >= n = l
              | otherwise = x : sieve list'
              where list' = filter (\y -> y `rem` x /= 0) xs

-- terceira implementação
primes'' :: Integer -> [Integer]
primes'' n = 2 : sieve 3 [3,5..n]
    where sieve :: Integer -> [Integer] -> [Integer]
          sieve _ [] = []
          sieve m l@(x:xs)
              | m*m >= n = l
              | x < m*m = x : sieve m xs
              | otherwise = sieve (m + 2) list'
              where list'= filter (\y -> y `mod` m /= 0) l

primes''TMD :: Integer -> [Integer]
primes''TMD n = 2 : sieve 3 [3,5..n]
    where sieve :: Integer -> [Integer] -> [Integer]
          sieve _ [] = []
          sieve m l@(x:xs)
              | m*m >= n = l
              | x < m*m = x : sieve m xs
              | otherwise = sieve (m + 2) list'
              where list'= filter (\y -> y `rem` m /= 0) l

Обратите внимание на улучшенное время выполнения вариантов с использованием rem:

 $ ghc --make -O2 sieve.hs
 $./sieve
 5000
 ...
 benchmarking primes 
 mean: 23.88546 ms, lb 23.84035 ms, ub 23.95000 ms

 benchmarking primes'
 mean: 775.9981 us, lb 775.4639 us, ub 776.7081 us

 benchmarking primes''
 mean: 837.7901 us, lb 836.7824 us, ub 839.0260 us

 benchmarking primesTMD
 mean: 16.15421 ms, lb 16.11955 ms, ub 16.19202 ms

 benchmarking primes'TMD
 mean: 568.9857 us, lb 568.5819 us, ub 569.4641 us

 benchmarking primes''TMD
 mean: 642.5665 us, lb 642.0495 us, ub 643.4105 us

Хотя я вижу, что вы делаете это для собственного образования, стоит обратить внимание на соответствующие ссылки Primes на Haskell.org и быстрый пакет Primes по взлому.

person Thomas M. DuBuisson    schedule 04.10.2010
comment
Вы не возражаете, если я использую одну из этих реализаций в проекте с открытым исходным кодом? - person Rob; 29.10.2013
comment
@robjb Этот код пришел от Янке, а не от меня. Если вам нужны простые числа в Haskell и вам нужна либеральная лицензия, посмотрите пакет простых чисел, который лицензирован. БСД3. - person Thomas M. DuBuisson; 29.10.2013

Это не оптимизированная, но выразительная реализация: посмотреть видео Решето Эратосфена в хаскеле

import qualified Data.Set as Set(fromList,difference)
kr n l = (*n) <$> [2..l `div` n]
g n = difference (fromList [2..n]) (fromList $ concat $ ((flip kr) n) <$> [2..n])
person Evg    schedule 18.10.2020
comment
это все очень загадочно. g lim = difference (fromList [2..lim]) (fromList [ m | n <- [2..lim], m <- [n+n, n+n+n .. lim]]) — это то же самое, но более очевидно, что он делает. :) - person Will Ness; 20.10.2020
comment
Кстати, это хороший подход. мы можем поиграть с ним немного, чтобы сделать его более эффективным. - person Will Ness; 21.10.2020
comment
Таким образом, 1_. выглядит хорошо. :) - person Will Ness; 21.10.2020
comment
@WillNess спасибо за улучшение! ты обалденный :) - person Evg; 21.10.2020