Оптимизировать код на Haskell, вычисляя сумму всех простых чисел ниже двух миллионов

Задача 10 в проекте Эйлера. Я видел некоторое обсуждение там, но только для C.

Я использовал следующий код для расчета:

print . sum . sieve $ [2..2000000] where
    sieve [] = []
    sieve (x:xs) = x : sieve (filter ((/= 0) . (`mod` x)) xs)

Это требует возраста, чтобы рассчитать. Мне интересно, есть ли более эффективный способ его расчета?

3 ответа

Решение

Многие действительно быстрые способы вычисления простых чисел в haskell описаны на странице haskellwiki для простых чисел. В частности, второй кажется достаточно хорошим, так что вы можете написать это так:

main = print . sum . takeWhile (< 2000000) $ primes 

primes = 2: 3: sieve (tail primes) [5,7..] 

sieve (p:ps) xs = h ++ sieve ps [x | x <- t, rem x p /= 0]  
  where (h,~(_:t)) = span (< p*p) xs 

Запустив его мы получим:

ghc --make -O2 Euler10.hs
time ./SOAns
142913828922

real    0m1.598s
user    0m1.577s
sys 0m0.017s

В вики описывается, почему ваше решение такое медленное, основная причина в том, что для каждого числа установлено сито до 2000000, когда достаточно одного для каждого простого числа.

Вы можете найти этот документ и последующее обсуждение интересным. Суть в том, что ваш sieve реализация не так эффективна, как "настоящее" сито Эратосфена.

Самый чистый оптимизированный код первичного просеивания, который я лично видел в Haskell, находится в пакете NumberSieves, который включает в себя как традиционное сито, основанное на изменяемых векторах, так и форму сита О'Нила. Не используйте ужасно сложный код в arithmoi пакет - по крайней мере, некоторые из них в настоящее время сломаны и случайным образом вызывают ошибки сегментации.

Другие вопросы по тегам