The first thing to try is enable compiler optimization level 2 by adding:
{-# OPTIONS_GHC -O2 #-}
to the start of your file. This reduced my execution time by 9%.
Next I removed the limits to take advantage of Haskell's laziness.
{-# OPTIONS_GHC -O2 #-}
-- doesn't work
import Data.List
marked :: [Int]
marked = [ i + j + 2*i*j | i <- [1..], j <- [i..]]
removeComposites :: [Int] -> [Int] -> [Int]
removeComposites [] _ = []
removeComposites (s:ss) [] = 2*s + 1 : removeComposites ss []
removeComposites (s:ss) (c:cs)
| s == c = removeComposites ss cs
| s > c = removeComposites (s:ss) cs
| otherwise = 2*s + 1 : removeComposites ss (c:cs)
sieveSundaram :: [Int]
sieveSundaram = 2:(removeComposites [1..] marked)
pe_007 = last (take n (sieveSundaram))
where n = 100001
main :: IO ()
main = do
print pe_007
Unfortunately that doesn't work because marked ends up being [4,7,10,13,16,19,22,25..] which is just i=1 and j<-[1..]. To solve this I generated a list for each i and merged the lists together using a fold.
{-# OPTIONS_GHC -O2 #-}
-- compiled execution time for 100001 is 0.877s (about 27% better)
marked :: [Int]
marked = fold $ map mark [1..]
where
fold ((x:xs):t) = x : (xs `union` fold t)
mark i = map (calc i) [i..]
calc i j = i + j + 2*i*j
union :: [Int] -> [Int] -> [Int]
union a [] = a
union [] b = b
union (x:xs) (y:ys)
| x < y = x : union xs (y:ys)
| x == y = x : union xs ys
| otherwise = y : union (x:xs) ys
removeComposites :: [Int] -> [Int] -> [Int]
removeComposites [] _ = []
removeComposites (s:ss) [] = 2*s + 1 : removeComposites ss []
removeComposites (s:ss) (c:cs)
| s == c = removeComposites ss cs
| s > c = removeComposites (s:ss) cs
| otherwise = 2*s + 1 : removeComposites ss (c:cs)
sieveSundaram :: [Int]
sieveSundaram = 2:(removeComposites [1..] marked)
pe_007 = last $ take n sieveSundaram
where n = 100001
main :: IO ()
main = do
print pe_007
In addition to reducing the execution time by a modest amount it also makes it simpler to debug things inside ghci. You could also change the main to take n as an command line input so you wouldn't need to recompile.
There is probably some inefficiency that I missed. For comparison, my Sieve of Eratosthenes code uses a similar merge fold and runs in 0.26s.
Update
I was curious about the drop in performance of my solution as N increased so I took some more time measurements. I also measured my Sieve of Eratosthenes code that uses a similar fold merge and a faster version I found that uses a treefold merge with a wheel.
10k 100k 200k 1M
Original: 0.073 1.210 2.788 20.983
My simple fold merge: 0.038 0.875 2.634 38.835
SoE simple fold merge: 0.028 0.270 0.646 6.214
SoE treefold merge with wheel: 0.019 0.079 0.142 0.900
HJulle's Unboxed STUarray: 0.015 0.021 0.038 0.172
So it is possible to make a sieve using lists that is quite a bit faster than the one I posted. I find it useful to have the infinite list version for use in other Project Euler problems so I can avoid issues with setting the upper bound incorrectly.
As far as my SoS code goes I'm not sure why it's so much worse than the SoE but I'm guessing it has to do with the double map in marked.
One last thing to note is the original uses much more memory (1.7GB for N=100k) than the other solutions.