I'm currently working on the 58th problem from Project Euler.
While my solution runs fine when I set the limit to 8%, it goes beyond one minute after that.
This surprises me, because I use a library for the primes generation and what I do by myself looks linear to me, and fine.
Though obviously I've missed plenty of huge thunks, quadratic (or worse) functions, bottlenecks etc before, and it must be the same here again.
What I'd like is a chek of my solution to know if the code is wrong, or if it's the way I handle the problem that's stupid. In the latter case, I'd like not to be spoiled.
I consider my question not to be a code review one, because basically my code doesn't work for its purpose, but I might be wrong, in which case please transfer the question to the appropriate SE site.
I tried literate programming for this one, so I'll just dump my file to provide further explanations.
My .lhs (well, formatted for SO)
We don't handle the primes ourselves and we want help from the compiler.
{-# OPTIONS_GHC -Wall #-}
import Data.Numbers.Primes
First, we construct in diags the stream of the numbers that are
on the diagonals. To do that, we notice that those numbers get incremented
4 times in a row by a certain number, and then again with this number + 2, etc.
replicate 4 [2, 4..] will give us a list of increments.
Then we just need to aggregate all that with a scanl (+) and there we have our list.
primesDiags :: [Int]
primesDiags = go diags primes
where
diags = scanl (+) 1 . concatMap (replicate 4) $ [2, 4..] :: [Integer]
Once we have this list, we map all the numbers to 0 if the number is composite and 1 if the number is prime. To do that efficiently, we use a library to provide the stream of primes and map the two lists by running through them once only.
go dss@(d:ds) pss@(p:ps) | d < p = 0:go ds pss
| d > p = go dss ps
| otherwise = 1:go ds ps
Then we tell ghc we know why our pattern matching is incomplete
go _ _ = undefined -- we operate on streams
Now we have everything we need to answer the problem. Next step is to find at which square we cross the specific limit we seek to spot. To do that we simply update an accumulator to represent the number of primes we met up until this point and we keep track of the index of the square we're at too.
We start the recursion at 2 to just keep track of factorized behaviour. That's why we skip one item in primesDiags and since this item is 1 we set our acc to 0 (1 isn't prime).
nthSquare :: Int
nthSquare = go 2 (tail primesDiags) 0
where
go n (w:x:y:_:ds) primeN | 8 * primeN' < compositeN' = n
| otherwise = go (n + 1) ds primeN'
where
total = 4 * n - 3
delta = sum [w, x, y]
primeN' = primeN + delta
compositeN' = total - primeN'
go _ _ _ = undefined -- we operate on streams
Then, once we've spot the correct square, its side length is obtained by doubling its index and substracting one.
main :: IO ()
main = print $ nthSquare * 2 - 1
Here is a paste if you want to play with the code.