AOC Day 4: Giant Squid

Day 4: Giant Squid

Spoilers ahead: this post contains solutions

The bingo boards are nicely readable in the text file, but we shall find them more tractable as vectors.

q)q:read0`:test4.txtq)nums:valuefirstq q)showboards:valueeach" "sv'(where0=counteachq)cutq22131711082234242191416761031851122015193150222918131751987252320111024414211612614211724410161591918823262022111365201237

A perfectly sensible looping approach would follow real life. We would call each number and see if any board has won.

We’re not going to do that. We’re going to call all the numbers and see where the wins occur.

s:(or\')boards=/:\:nums / states: call all the numbers in turn

The derived function =/:\: (Equal Each Right Each Left) gives us a cross-product on the Equal operator. The list boards=/:\:nums has an item for each board. Each item is a boolean matrix: a row for each of the called numbers, the columns corresponding to the board numbers. Here’s the first board with 1s flagging the numbers as they are called in turn.

q)firstboards=/:\:nums0000000000000010000000000b0000000010000000000000000b0000000000010000000000000b0000000000000000000100000b0001000000000000000000000b0010000000000000000000000b..

Of course, once a number is called, it stays called.

q)(or\)firstboards=/:\:nums0000000000000010000000000b0000000010000010000000000b0000000010010010000000000b0000000010010010000100000b0001000010010010000100000b0011000010010010000100000b..

That is just the first board. We want the same for every board.

s:(or\')boards=/:\:nums / states: call all the numbers in turn

How to tell if a board has won? Here is the state of board 0 after 9 numbers have been called.

q)s[0;9;]0011101110011010000100000b

Has it won?

q)5cuts[0;9;]00111b01110b01101b00001b00000b

Clearly not. That would require all 1s on at least one row. Or a column.

q)anyalleach{x,flipx}5cuts[0;9;]0b

Now we can flag the wins.

q)showw:{anyalleach{x,flipx}5cutx}''[s]000000000000011111111111111b000000000000001111111111111b000000000001111111111111111b

From this we can see that the third board is the first to win and the second is the last to win. Also that they win on (respectively) the 11th and 14th numbers called.

q)sumeachnotw131411i

So the state of the winning board is s[2;11;]

q)5cuts[2;11;]11111b00010b00100b01001b11001bq)sumboards[2]wherenots[2;11;] / sum the unmarked numbers188q)nums[11]*sumboards[2]wherenots[2;11;] / board score4512

That makes our complete solution:

q:read0`:day4.txtnums:valuefirstqboards:valueeach" "sv'(where0=counteachq)cutqs:(or\')boards=/:\:nums / states: call all the numbers in turnw:sumeachnot{anyalleachb,flipb:5cutx}''[s] / find winsbs:{nums[x]*sumboards[y]wherenots[y;x;]} / score for board y after xth numbera[`$"4-1"]:bs.{m,x?m:minx} w / winning board scorea[`$"4-2"]:bs.{m,x?m:maxx} w / losing board score

This is a nice example of the ‘overcomputing’ characteristic of vector languages. Clearly, an efficient algorithm would stop when it reached the first win. Or, for Part 2, the last win.

But sometimes it is convenient to calculate all the results and search them. And, with vector operations, sometimes it is faster as well.