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.
Solution
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
where
maxPalindrome [] = 0
maxPalindrome (x':xs') | isPalindrome m = m
| otherwise = maxPalindrome xs'
where
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.