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.
Not that the rest cannot be improved at all, but the library you used for prime generation isn’t too fast. Using your code, I got
Changing the import to
(from the
arithmoipackage – disclaimer, I’m the author) and the first line ofprimesDiagstothe result is
which shows that your part of the code is decent. You can improve it by using the fact that on one of the spikes there are the squares, so that that needn’t be considered at all.
Another point is that instead of computing all primes, you could just inspect the values on the diagonals (ignoring the squares) and check which of them are prime if you have a fast prime check. Whether that’s faster depends on the prime check.