euler !! 7



This one doesn't warrant much discussion:

Problem 16:
What is the sum of the digits of the number 2^1000?

problem15 = sum . digits (2^1000) where
    digits n = map (\x -> read [x] :: Int) $ show n

Now for a much more interesting problem (deja vu?):

Problem 18:
Find the route from top to bottom with the largest sum in the triangle below:
   75
95 64
...

The triangle has 15 rows, so it's not unreasonable to simply brute force it. We could go about this by recursively asking a node in the triangle for the greater of its two child routes, e.g. a function like this:

maxRoute node
    | noChildren node = node
    | otherwise       = node + max (maxRoute leftChild) (maxRoute rightChild)

Unfortunately, we can't just pass a simple number to maxRoute, because there would be no way of determining what leftChild and rightChild are. I'm sure we could accomplish it using some sort of custom-defined type, but I haven't read up on defining custom types in Haskell. Instead, we'll just pass a pair of coordinates, which we can use to lookup the value in our triangle data structure (which will be a simple two-dimensional list):

maxRoute row col
    | row == 14 = tri !! row !! col
    | otherwise = tri !! row !! col + max left right where
        left  = maxRoute (row + 1) col
        right = maxRoute (row + 1) (col + 1)
    tri = [[75],
           [95,64],
           [17,47,82],
           ...

Then a simple problem18 = maxRoute 0 0 will compute the answer.

A more sophisticated approach would be to go "bottom up" instead of "top down," as in the brute force strategy. Starting at the bottom of the triangle, we reduce each parent-left-right set with parent = parent + max left right. Then we can simply fold this operation upwards until we reach the top of the triangle, at which point we'll have our answer.
This algorithm winds up looking a bit more complex, mainly because now we're processing two whole rows at a time instead of just three nodes. That means we'll be using zipWith3, which works as you might expect. The function we'll be zipping with is exactly what we described above, and the list arguments will be the parent row, the child row, and the child row with an offset of 1 (via tail).

problem18 = head $ foldr1 maxRow tri where
    maxRow pRow cRow = zipWith3 reduce pRow cRow (tail cRow)
    reduce parent left right = parent + max left right
    tri = [[75],
           [95,64],
           [17,47,82],
           ...

Note that we have to use foldr instead of the usual foldl since we're starting at the bottom.

Let's finish off this post with another easy one:

Problem 20:
Find the sum of the digits in the number 100!

This is as easy as:

problem20 = sum . digits $ factorial 100

...except that we haven't defined a factorial function yet. In an earlier post, we covered a few methods of implementing a factorial function in Perl. As it turns out, it's even more fun in Haskell (in fact, there's an entire web page devoted to it). Let's cover just a few:

fac 0 = 1
fac n = n * fac (n - 1)

fac n = product [1..n] -- essentially syntactic sugar for:
fac n = foldl1 (*) [1..n]

facs = scanl (*) 1 [1..]
fac n = facs !! n

Efficiency-wise, it doesn't make much difference which implementation you choose, but if you're going to computing lots of values, the memoized version (that last one) is probably your best bet. Memoization means that before carrying out the factorial computation, we first check to see if we already know the answer. Then we can just use that instead of recalculating it from scratch. In most languages, a memoized implementation of factorial will be quite a bit more verbose, but it comes almost for free in Haskell because of the lazy nature of infinite lists. fac is just looking up a value in facs, and when we ask facs for an entry it hasn't calculated yet, it carries out the necessary computation. So asking for fac 10000 might take a bit, but after that you'll be able to look up any factorial under 10000 almost instantly. Pretty cool!

Leave a Reply

Theme: Esquire by Matthew Buchanan.