2009-05-22 2 views
6

Итак, я работал над тем, чтобы лениво генерировать простые числа, и я придумал эти три определения, которые все работают эквивалентным образом - просто проверяя, имеет ли каждое новое целое число фактор среди всех предыдущих штрихи:Haskell style/efficiency

primes1 :: [Integer] 
primes1 = mkPrimes id [2..] 
    where mkPrimes f (x:xs) = 
      if f (const True) x 
      then 
      let g h y = y `mod` x > 0 && h y in 
      x : mkPrimes (f . g) xs 
      else 
      mkPrimes f xs 

primes2 :: [Integer] 
primes2 = mkPrimes id (const True) [2..] 
    where mkPrimes f f_ (x:xs) = 
      if f_ x 
      then 
      let g h y = y `mod` x > 0 && h y in 
      x : mkPrimes (f . g) (f $ g $ const True) xs 
      else 
      mkPrimes f f_ xs 

primes3 :: [Integer] 
primes3 = mkPrimes [] [2..] 
    where mkPrimes ps (x:xs) = 
      if all (\p -> x `mod` p > 0) ps 
      then 
      x : mkPrimes (ps ++ [x]) xs 
      else 
      mkPrimes ps xs 

Так мне кажется primes2 должны быть немного быстрее, чем primes1, так как это позволяет избежать повторного вычисления f_ = f (const True) для любого целого (которое я думаю требует работы по порядку числа простых чисел мы 's найденный до сих пор), и только обновляет его, когда мы enc одурачить новый премьер.

Просто от ненаучных испытаний (бег take 1000 в GHCI), похоже primes3 работает быстрее, чем primes2.

Должен ли я взять из этого урок, и предположу, что, если я могу представить функцию в качестве операции на массиве, что я должен выполнять его последний способ для повышения эффективности, или есть что-то еще здесь происходит ?

+0

(как я уверен, вы уже знаете) в 'primes3', здесь вызов' all' является огромным излишеством - брать только простые числа, не превышающие 'sqrt'' x', - таким образом, можно использовать тот же список простых чисел который создается - функция становится простым фильтром: 'primes4 = 2: filter (\ x-> all ((/ = 0). (rem x)) $ takeWhile ((<= x). (^ 2)) primes4) [3,5 ..] ', работающий примерно на' O (n^1.45) 'эмпирически, в' n' простых числах - * все три версии в вашем вопросе выглядят квадратично - независимо от того, как вы строите свои функции, они все еще test by * all * primes вместо тех, что ниже 'sqrt x'. –

ответ

9

Каков второй аргумент в пользу f? На мой взгляд, оба из этих альтернатив более удобным для чтения, и не оказывают существенного влияния на производительность ...


Во всяком случае, вернемся к этому вопросу. Иногда использование функций в качестве структур данных является лучшим представлением для определенной задачи, а иногда и нет. «Лучшее» с точки зрения простоты кодирования и «лучшего» с точки зрения производительности не всегда одно и то же. Методика «функции как структуры данных» имеет важное значение для runtime compilation, но эта страница предупреждает,

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

В вашем случае, вероятно, что накладные расходы на строительство каждого f :: Integer -> ... -> Bool значительно выше, чем накладные расходы на строительство каждого ps :: [Integer], с небольшим или без разницы при вызове f ... x против all ... ps.


Чтобы сжать циклы из бесконечного простого решета, чтобы избавиться от вызовов mod! Целочисленное умножение, деление и модуль намного медленнее, чем целочисленное сложение и вычитание. На моей машине эта реализация работает на 40% быстрее при расчете первых 1000 простых чисел (GHC 6.10.3 -O2).

import qualified Data.Map as M 
primes' :: [Integer] 
primes' = mkPrimes 2 M.empty 
    where 
    mkPrimes n m = case (M.null m, M.findMin m) of 
     (False, (n', skips)) | n == n' -> 
      mkPrimes (succ n) (addSkips n (M.deleteMin m) skips) 
     _ -> n : mkPrimes (succ n) (addSkip n m n) 
    addSkip n m s = M.alter (Just . maybe [s] (s:)) (n+s) m 
    addSkips = foldl' . addSkip 

В действии (с использованием немного JSON-иш синтаксис),

mkPrimes 2 {} 
=> 2 : mkPrimes 3 {4: [2]} 
=> 2 : 3 : mkPrimes 4 {4: [2], 6: [3]} 
=> 2 : 3 : mkPrimes 5 {6: [2, 3]} 
=> 2 : 3 : 5 : mkPrimes 6 {6: [2, 3], 10: [5]} 
=> 2 : 3 : 5 : mkPrimes 7 {8: [2], 9: [3], 10: [5]} 
=> 2 : 3 : 5 : 7 : mkPrimes 8 {8: [2], 9: [3], 10: [5], 14: [7]} 
=> 2 : 3 : 5 : 7 : mkPrimes 9 {9: [3], 10: [2, 5], 14: [7]} 
=> 2 : 3 : 5 : 7 : mkPrimes 10 {10: [2, 5], 12: [3], 14: [7]} 
=> 2 : 3 : 5 : 7 : mkPrimes 11 {12: [2, 3], 14: [7], 15: [5]} 
... 

карта отслеживает будущих мультипликаторов, не используя ничего, кроме того.

+0

Спасибо! Это тот подробный ответ, на который я надеялся. – rampion

+0

: просто [это может быть значительно улучшено] (http://hpaste.org/79571), задерживая добавление каждого штриха в карту до тех пор, пока квадрат первого квадрата не появится на входе, как видно, например, [здесь, на Python] (http://stackoverflow.com/a/10733621/849891). также сравните http://stackoverflow.com/a/13895347/849891. –

1

Отметьте, что primes3 можно сделать более эффективным, изменив ps++[x] на (x:ps).Пробег (++) является линейным по длине его левого аргумента, но постоянным по длине правого аргумента.

+3

Собственно, это было намеренно. 2 является фактором гораздо чаще, чем 173, поэтому мы получаем более ранние выходы при проверке на первичность, когда начинаем с малого конца, чем с большого конца. – rampion