I have this code, which is a pseudo-Sieve of Eratosthenes for generating primes:
module Prime where
import Data.List (unfoldr)
nth :: Int -> Maybe Integer
nth n
| n < 1 = Nothing
| otherwise = Just $ primes !! (n - 1)
primes :: [Integer]
primes = unfoldr (Just . f) [2..]
where
f :: [Integer] -> (Integer, [Integer])
f cands = (next, filter (\n -> n `mod` next /= 0) (tail cands))
where
next = head cands
This code is correct, but it's quite slow - it takes about 6 seconds to generate the 10,001st prime. A simple algorithm, without fancy performance work, that I picked off the Haskell Wiki can find the same prime in a fraction of a second.
As per the limited profiling I've done, it doesn't seem there's any space leaks, but rather genuine algorithm slowness. What is the source of this slowness?
I'm looking for minimal changes that can be simply understood, to target the inefficiency and acheive a decent speedup.
EDIT
@arrowd suggested that the extra work involved in re-traversing the list using !! was wasted. But a version of the algorithm without this is even slower:
nth' :: Int -> Integer
nth' = loop [2..]
where
loop :: [Integer] -> Int -> Integer
loop cands 1 = head cands
loop cands n = loop (filter (\n' -> n' `mod` head cands /= 0) cands) (n - 1)
Data.List.(!!)is O(n). \$\endgroup\$headis O(n) too \$\endgroup\$Data.Vector. If you still want to do it with lists as a learning exercise, I would suggest traversing from back to front, appending each non-multiple from the current list to the head of the updated list with a tail-recursive-modulo-cons function. \$\endgroup\$