I am trying to solve Project Euler #95 in Haskell, though I have already solved it in Java (see). I found that writing imperatively, we can easily do the task this way:
Pseudocode
- Define cache as an
int
array. - Find all perfect numbers and store them in a
HashSet
. - For each number create a new
HashSet
for terms (namedterms
) that have already occured while iterating. - If the number is prime or perfect skip it (and set
cache
value to-1
) - Now iterate using sum of proper divisors, if we stumble upon a number that is out of bounds or has
cache
value-1
skip this too and set this number'scache
value as-1
too. Iterate until we reach 1 or a perfect number or the number itself; keep adding toterms
. - If we reach the number, get the size of
terms
, otherwise (1 or perfect number) setcache
value to-1
. - Keep record of number with maximum
terms
size.
Now thinking of functionally writing this solution I find it quite difficult to maintain something as cache
, I have to iterate for each number. Also, my mind was quite stuck to this imperative solution so the Haskell equivalent was not quite different or unique. How can I improve it? (It is also quite slower - 32s)
95.hs
import Control.Arrow
import qualified Data.IntSet as Set
import Data.List
import Data.Ord
import Divisors
lim :: Int
lim = 1000000
main :: IO ()
main = print . fst . maximumBy (comparing snd) . map (id &&& amicableChainLength) $ [2..lim]
where
amicableChainLength x
| sumOfProperDivisors x == x = 1 -- perfect numbers
| otherwise = (\((_, list), _) ->
case elemIndices (last list) list of
-- Two occurences
(a:b:_) -> b - a
-- Cannot occur but anyways
_ -> 0
)
-- Take first element where include stopped
. head . dropWhile snd . scanl (include x)
-- ((Set of numbers observed, Ordered list of them), Continue?)
((Set.empty, []), True)
$ iterate sumOfProperDivisors x
-- tries to include number into set/list and also tell when to stop
include x ((set, list),_) n
-- number is member of observed numbers
| Set.member n set =
if x==n
-- chain should start at that number
then ((set, list ++ [n]), False)
-- otherwise no meaning
else ((Set.empty, []), False)
-- Out of bounds or an number earlier encountered, this cannot be answer
-- since we are required to find minimum element of that chain
| n > lim || n < 2 || n < x = ((Set.empty, []), False)
-- New number, add it & continue
| otherwise = ((Set.insert n set, list ++ [n]), True)
Divisors.hs
module Divisors where
sumOfProperDivisors :: Int -> Int
sumOfProperDivisors n = subtract n . (\s -> if isPerfectSquare n then s - root else s)
. sum . map (\x -> x + n `div` x) . filter (\x -> n `mod` x == 0)
. takeWhile (\p -> p*p <= n) $ [1..]
where
root = round (sqrt . fromIntegral $ n :: Double)
isPerfectSquare x = root * root == x