Problem 11 brings the most interesting bit of list processing so far.
In the 20x20 grid below, four numbers along a diagonal line have been marked.
The product of these numbers is 26 x 63 x 78 x 14 = 1788696.
What is the greatest product of four adjacent numbers in the same direction
(up, down, left, right, or diagonally) in the 20x20 grid?
Lots of fun to be had in this one, twisting the grid around this way and that.
We can re-use the window function from problem 8 to get a sliding list of
values over each row easily enough. We just have to manoeuvre the grid to present
all the possible sets of four.
Horizontal rows
The first lot are easy enough; just call window along each of the rows
of the grid exactly as is. Start in the upper left with [8, 2, 22, 97]
and take it from there.
Vertical columns
The next thing to do is get the values down the columns of the grid.
For this we need to flip it along its diagonal to bring the verticals
into the horizontal. A new function, then; transpose.
The results are simply flipping each vertical into the horizontal.
Combined with the window function we can check the groups of four in the verticals.
The next bit is where it gets really interesting; a sliding window across
the diagonals.
Diagonals
Calculating the diagonals turned out to be pretty tricky. The basic issue is that
for each half of the grid, split diagonally from top left to bottom right, there
are two sets of values that need to be retrieved.
To get the first set, we strip in incrementing number of values from the start
of each consecutive row, then transpose the result, like this.
As you can see, this produces the centre diagonal line and the three lines
above it toward the top right corner.
To get the other half, we need to similarly remove some values, but this time
by reversing the row first and removing the most from the first row.
Note that the first row is the same for both sets of diagonals. We can simply
ignore the first row for the first set of diagonals.
To perform this feat of stripping and transposing I needed to create quite a
few utility functions to give clear meaning to the expressions. Here they are.
zip_with(fn, *seqs)
Zip all the sequences and map the tuples with a function.
reverse(seq)
Like the built-in reversed function, but works on any sequence.
transpose_irregular(seq)
Like transpose but do not truncate to the shortest sub-array. Instead fill in
blanks with None.
filter_none(seq)
Remove all entries in a sequence that are None.
With this small arsenal of utilities I can start putting together the
diagonal generator.
And there they are.
Of course, there are the other set of diagonals, the ones that run bottom
left to top right. These are generated in the same way but with the input
array reversed.
Calculating the products
We now have enough to generate a full list of inputs for the window function
using rows, columns and diagonals in both directions. The next step is to take
each set of inputs and convert the lists to a set of products using the window
and product functions. We can use the itertools.chain function to convert
the various input sequences into one long set of inputs.
This solution produces the correct answer 70600674 in 3ms.