Project Euler in Haskell #4

2012-04-20 — Article of 700 words - Available in Italiano Programming Haskell Project Euler

Problem Description

Link to Project Euler problem 4

A palindromic number reads the same both ways. The largest palindrome made from the product of two 2-digit numbers is $9009=91\times99$.

Find the largest palindrome made from the product of two 3-digit numbers.

WARNING Solution ahead. Don’t read more if you want to enjoy the benefits of Project Euler and you haven’t already solved the problem.


First of all, we need a function to verify if a number is palindrome. With isPalindrome we simply convert the number to a string and compare the string with its reverse.

Note that this function applies not only to numbers but to types that implements the Show typeclass.

isPalindrome    :: Show a => a -> Bool
isPalindrome x  =  x' == reverse x' where x' = show x

The simple solution to the problem uses Haskell list comprehension. We enumerate all the products of two 3-digit numbers, filter the palindromes, and extract the largest one.

solution  :: Integer
solution  =  maximum  [x*y | x <- [100..999], y <- [100..999]
                      , isPalindrome (x*y)]

We can improve this solution, noting that the combination of the same terms is considered two times, e.g. x=123,y=456 and x=456,y=123. We can filter the potential palindromes to only include the products where the first term is more than or equal to the second term.

solution'  :: Integer
solution'  =  maximum  [x*y | x <- [100..999], y <- [100..999]
                       , x >= y
                       , isPalindrome (x*y)]

Moreover, if we express the palindromes as a six terms multi-variable polynomial, after some simplifications we find out that all the palindromes of 6-digit are divisible by 11.

$$ P=100000x+10000y+1000z+100z+10y+x $$ $$ P=100001x+10010y+1100z $$ $$ P=11(9091x+910y+100z) $$

We can filter the potential palindromes to only include the products where at least one of the terms is divisible by 11.

solution''  :: Integer
solution''  =  maximum  [x*y | x <- [100..999], y <- [100..999]
                        , x >= y
                        , x `mod` 11 == 0 || y `mod` 11 == 0
                        , isPalindrome (x*y)]

But, if we want to find an efficient solution, we have to change method. Counting downwards from 999, we can find the solution earlier.

The euler4 function recurses on a list of decreasing numerical terms and, with the current term fixed as the first term, executes an inner recursion using the rest of the list as the source for second terms. The resulting product is verified to find out if it is a palindrome. The result of the inner recursion is compared with the candidate palindrome to choose the largest one.

The euler4 function uses two optimization techniques:

  • it exits from the inner recursion when it finds the first palindrome: given that the first term of the product is fixed, it could not find a larger palindrome
  • it exits from the outer recursion when the candidate palindrome is larger than the square of the first term: given that the subsequent terms are smaller, it could not find a larger palindrome
euler4               :: (Ord a, Num a) => [a] -> a -> a
euler4 []         p  =  p
euler4 ns@(x:xs)  p  |  x*x < p    =  p
                     |  otherwise  =  euler4 xs $ max p $ maxPalindrome ns
                       maxPalindrome []        =  0
                       maxPalindrome (x':xs')  |  isPalindrome m  =  m
                                               |  otherwise       =  maxPalindrome xs'
                                                 m = x * x'

We find the solution calling euler4 with a list from 999 to 100 and with 0 as the candidate palindrome.

solution'''  :: Integer
solution'''  =  euler4 [999,998..100] 0

Note that euler4 could be used to find the largest palindrome made from the product of number with more than 3-digit, e.g. euler4 [9999,9998..1000] 0.

You can find the Literate Haskell code on GitHub and on Bitbucket.