2
\$\begingroup\$

Project Euler #3 asks:

The prime factors of 13195 are 5, 7, 13 and 29.

What is the largest prime factor of the number 600851475143 ?

I've written a general solution for any number:

module Main where

isDiv :: Integer -> Integer -> Bool
n `isDiv` k = n `mod` k == 0

fullyDiv :: Integer -> Integer -> Integer
fullyDiv n k
    | n `isDiv` k = fullyDiv (n `div` k) k
    | otherwise = n

intSqrt :: Integer -> Integer
intSqrt = floor . sqrt . fromIntegral

maxPrimeFact :: Integer -> Integer
maxPrimeFact n = go n (1, wheel)
    where
        wheel = 2 : [3,5..intSqrt n]
        go n (x, []) = if n == 1 then x else n
        go n (x, (y:ys))
            | n `isDiv` y =
                let o = fullyDiv n y
                in go o (y, takeWhile (<= intSqrt o) ys)
            | otherwise = go n (x, ys)

problem3 :: Integer -> Integer
problem3 = maxPrimeFact

main :: IO ()
main = do
    _ <- getLine
    contents <- getContents
    let cases = map read $ lines contents
    let results = map problem3 cases
    mapM_ print results

An explanation of this algorithm can be found at my answer.

I'd appreciate any comments on rewriting maxPrimeFact as it seems unwieldy to me.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ cf. this answer with a pseudocode which should be easy enough to turn into a valid Haskell. \$\endgroup\$
    – Will Ness
    Commented Jan 6, 2015 at 22:40

1 Answer 1

1
\$\begingroup\$

You don't need to terminate the list of candidate factors. Haskell, being a lazy language, deals with infinite lists just fine.

In Haskell, it is common to use pattern matching instead of if … then … else.

There is not much advantage to having your go helper take an Integer (Integer, Integer) rather than three Integers.

You are repeating the isDiv test in fullyDiv and go. Instead of a fullyDiv function, you can just fold the retry logic into go itself.

The names x, y, and o are hard to follow. I suggest maxPrime instead of x, and w instead of y (the mnemonic being the first character of wheel).

isDiv :: Integer -> Integer -> Bool
n `isDiv` k = n `mod` k == 0

maxPrimeFact :: Integer -> Integer
maxPrimeFact n = go n 1 (2 : [3, 5..])
  where
    go 1 maxPrime _ = maxPrime
    go n maxPrime wheel@(w:ws)
      | n `isDiv` w = go (n `div` w) w wheel
      | otherwise   = go n maxPrime ws

But you should be able to rewrite it further by taking advantage of divMod.

maxPrimeFact :: Integer -> Integer
maxPrimeFact n
  | n < 2     = error ("Invalid n=" ++ (show n))
  | otherwise = go n 1 wheel
  where
    wheel = 2 : [3, 5..]
    go 1 largestPrimeFactor _ = largestPrimeFactor
    go n largestPrimeFactor (w:ws)
      | n `isDiv` w = go (n `div` w) w (w:ws)
      | otherwise   = go n largestPrimeFactor ws
\$\endgroup\$
4
  • \$\begingroup\$ It appears that the go function you wrote would iterate all the way till the maximum prime. The reason why I wrote the function as such was to reduce the search space (refer to my answer which I referred above). Otherwise, thanks for the excellent suggestions! \$\endgroup\$
    – wei2912
    Commented Dec 24, 2014 at 4:24
  • \$\begingroup\$ The program completes in 0.02 sec. I wouldn't bother with premature optimization. \$\endgroup\$ Commented Dec 24, 2014 at 4:32
  • \$\begingroup\$ I'm actually using this program in hackerrank.com/contests/projecteuler/challenges where it errors out in the last case: hackerrank.com/contests/projecteuler/challenges/euler003/…. \$\endgroup\$
    – wei2912
    Commented Dec 24, 2014 at 6:45
  • \$\begingroup\$ I made the change and it passes all tests: hackerrank.com/contests/projecteuler/challenges/euler003/…. Anyways, thanks for the new maxPrimeFact function; I'll mark your answer as accepted. \$\endgroup\$
    – wei2912
    Commented Dec 24, 2014 at 6:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.