Actually, you did a much better job with your first version than you seem to think.
Your Python version takes about 23 seconds (Python 3) on my desktop. If I take your original program:
toDigits :: Int -> [Int]
toDigits 0 = []
toDigits x = toDigits (x `div` 10) ++ [x `mod` 10]
isSumofFifth n = n == sum (map (^5) (toDigits n))
main = print $ sum (filter isSumofFifth [9..9999999])
and, remembering that Haskell is a compiled language, take care to compile it with optimizations ghc -O2 FindFives.hs
(using GHC 8.6.4), I find the executable runs in about 2.7 seconds, so about ten times faster than the Python version.
So, Important Note: Never benchmark Haskell code with the GHCi interpreter or using runghc
! The performance results will be completely meaningless.
Also, since this is Code Review, let me point out that you can write your Haskell version in much the same form as the Python version:
answer :: Int
answer = sum [i | i <- [9..9999999], i == sum [d^5 | d <- toDigits i]]
-- Python: sum([i for i in range(9,9999999) if i == sum([int(x)**5 for x in str(i)]
Using your original toDigits
, this still runs in about 2.7 seconds, but it's probably easier to read.
@Truman has covered the main ways to speed up your toDigits
. The version in that answer gives a runtime of about 1 second.
You can do a little better. The version:
toDigits :: Int -> [Int]
toDigits = unfoldr go
where go 0 = Nothing
go x = Just $ swap $ x `quotRem` 10
gets it down to about 700ms because unfoldr
allows the creation of the actual digits list to be optimized away.