Consider the following imperative code which finds the largest palindrome among products of 3-digit numbers (yes, it's the one of the first tasks from "Project of [outstanding mathematician of 18th century]" site):
curmax = 0
for i in range(999,100):
for j in range(999,100):
if ((i*j) < curmax): break
if (pal(i*j)):
curmax = i*j
break
print curmax
As I'm learning Haskell currently, my question is, how do you translate this (and basically any imperative construct that contains something more complex than just plain iteration, e.g. breaks, continues, temporary variables and all this) to Haskell?
My version is
maxpal i curmax
| i < 100 = curmax
| otherwise = maxpal (i-1) (innerloop 999)
where
innerloop j
| (j < 100) || (p < curmax) = curmax
| pal p = p
| otherwise = innerloop (j-1)
where p = i*j
main = print $ maxpal 999 0
but this looks like we're still in imperative uglytown.
So what could you advise, what are the approaches of dealing with such cases FP-style?
If we do away with all the optimization and just multiply all combinations of numbers between 100 and 999, filter out the non-palindromes and take the maximum of that, we can write the function very concisely as:
maximum $ filter pal [x*y | x <- [100..999], y <- [100..999]]
Of course this is basically the least efficient way to do it, but since the numbers are relatively small, this still finishes in under half a second on my machine.
However if we want something that is more along the lines of your python solution algorithmically, we can do it like this:
import Data.Maybe
import Data.List
maxpal i curmax
| i < 100 = curmax
| otherwise = maxpal (i-1) newmax
where newmax = fromMaybe curmax (find pal bigger)
bigger = takeWhile (> curmax) (map (*i) [999, 998 ..])
Here the outer loop is basically the same as in your solution, but we replaced the inner loop using list functions instead.
We're using map (*i) [999, 998, ...]
to create the product i*j
for every j
counting down from 999
. Using takeWhile
we're saying that the list should stop once a value is not greater than curmax
.
Then we're using find
to see whether any item in that list is a palindrome. If it is, the first palindrome in the list is our new max. If it isn't we keep our old max. (find
returns a Maybe
and fromMaybe
takes a default value and a Maybe
and returns the value from the Maybe
or the default value if there is no value in the Maybe
)