Written by Pete Corey on Dec 14, 2018.

Advent of Code’s day eleven puzzle asks us to compute a three hundred square grid of values and to find the three by three sub-grid that contains the highest sum of values. In my heart of hearts I knew that this would be a problem well suited to the J programming language.

I started by working on a verb to break a grid into a number of sub-grids of a given size. This reminded me quite a bit of Elixir’s Enum.chunk_every/4 function, so I decided to name it accordingly:

    grid =. 5 5 $ i. 25
    grid
 0  1  2  3  4
 5  6  7  8  9
10 11 12 13 14
15 16 17 18 19
20 21 22 23 24
    chunk_every =. [ <\"2 [: |:"2 [: > <\ 
    3 chunk_every grid
┌────────┬────────┬────────┐
│0 5 10  │1 6 11  │2 7 12  │
│1 6 11  │2 7 12  │3 8 13  │
│2 7 12  │3 8 13  │4 9 14  │
├────────┼────────┼────────┤
│5 10 15 │6 11 16 │7 12 17 │
│6 11 16 │7 12 17 │8 13 18 │
│7 12 17 │8 13 18 │9 14 19 │
├────────┼────────┼────────┤
│10 15 20│11 16 21│12 17 22│
│11 16 21│12 17 22│13 18 23│
│12 17 22│13 18 23│14 19 24│
└────────┴────────┴────────┘

To be totally transparent, I originally came up with this verb by tinkering in the REPL, and converted it into a tacit verb after the fact.

Now that we have chunk_every, we can define a few more needed values, like our initial grid, our grid size, and our grid serial number:

    size =. 300
    grid =. (size,size) $ i. size * size
    grid_serial_number =. 9424

The puzzle tells us how to convert our initial grid’s x/y coordinates into “fuel cell values”. Let’s write an init verb that takes our initial verb and calculates and populates those values:

    init =. 3 : 0
      'cy cx' =. (0,size)#:y
      rack_id =. cx + 10
      power =. rack_id*cy
      power =. power + grid_serial_number
      power =. power * rack_id
      power =. 10 | power <.@% 100
      power =. power - 5
      power
    )

Now we’re ready to start. We’ll begin by generating our grid of fuel cells:

    grid =. init"0 grid

Next, we’ll break our grid into three by three chunks:

    chunks =. 3 chunk_by grid

Once we have our sub-grids, we’ll calculate the sum of each and flatten that into a one dimensional array of sums:

    flat_chunks =. , +/"1 ,"2 > chunks

And find the maximum sub-grid sum:

    max =. >./ flat_chunks

And the corresponding index of that maximum sum:

    highest =. flat_chunks i. >./ flat_chunks

Finally, we can turn that index into a pair of x/y coordinates:

    |. (0,(size-2))#:highest

This is the answer to our puzzle. Victory!

Our fuel cells, visualized.

Just for fun, we can check out what our newly initialized fuel cell matrix looks like with the help of viewmat. We can see some cool Moiré patterns in the resulting visualization as a side effect of our calculations.

Part Two

Part two wants us to vary the size of our sub-grids, and find the sub-grid size, and x/y coordinate pair that has the most amount of available fuel, or the highest sum.

My first instinct was to run chunk_by multiple times against chunk sizes ranging from 1 to 300:

    chunks =. (grid&(<@:chunk_by~))"0 (1 + i. size)

I wrote a verb to count the amount of available fuel within each new set of sub-grids, and ran that against all of the sub-grid sets I was generating:

    count =. 3 : 0
      chunks =. > y
      chunk_size =. # 0 0 {:: chunks
      flat_chunks =. , +/"1 ,"2 > chunks
      max =. >./ flat_chunks
      highest =. flat_chunks i. >./ flat_chunks
      coord =. |. (0,(size-(chunk_size-1)))#:highest
      max;coord,chunk_size
    )

    counts =. count"_1 chunks

From there, I could grab the best score of all of the sub-grid sizes, find the max, and return a tuple of that winning sub-grid’s size, and its x/y coordinate:

    scores =. > 0 {"1 counts
    idx =. scores i. >./ scores
    1 {:: idx { counts

Unfortunately, this turned out to be too slow of a solution.

Thankfully, there were some patterns to be found that let us speed things up considerably! If we plot our counts with a max grid size of 10, 50, and 100, we can see that our score always peaks almost immediately. We don’t need to test larger sub-grid sizes because we know our answer won’t be there.

A plot of our sub-grid size vs our maximum fuel availability.

Let’s change our solution to only check sub-grid sizes from one to twenty:

    chunks =. (grid&(<@:chunk_by~))"0 (1 + i. size)

And just like that, we get an answer almost immediately.

Thanks J!