AOC 2021 - Day 4
You're already almost 1.5km (almost a mile) below the surface of the ocean, already so deep that you can't see any sunlight. What you can see, however, is a giant squid that has attached itself to the outside of your submarine.
Maybe it wants to play bingo?
Watery grave = 95%
Times for today: 10:42, 14:08. I recorded my screen and intend to do a little analysis of where I spent my time. I used screen2gif and ffmpeg (but had a hiccup when I filled up my temp drive in the middle.) I’m generally happy with my code, although I could definitely have named things better in a couple places. I always reach for l
, i
, j
, ii
, etc too often, although this time I managed to not confuse myself too badly.
I’d classify this problem as “game modelling” and “array operations”, both of which I’m familiar with and enjoy.
Screencap analysis (played back at 2x real time, all times in video time):
- 00:00 -> 00:39: I set up my environment and timers and stuff. I made a new directly weirdly; usually I would use the command line for this. Otherwise, this is as usual.
- 00:39 -> 00:58: I read the problem.
- 00:58 -> 01:13: I copied the test case and defined the main function
- 01:14 -> 01:37: Splitting input data into numbers and boards
- 01:37 -> 01:57: more problem reading and getting the input data. This could have waited for later, probably an unnecessary context switch
- 01:57 -> 3:00: Defining the main logic of the function; iterate through the numbers, marking them called in each board and checking if there’s a winner. This is where I could have sped up considerably with better typing skills; 2 minutes real time to type 20 LOC?
- @2:42: My screen recorder stopped and I took a pause to fix it. My temp dir had run out of space, I really need a new laptop.
- 3:00 -> 4:10: defining the string to board mapping. I defined the data model in my head while writing the function. I probably could have saved a lot of time by using np.loadtxt or similar.
- 4:10 -> 4:44: defining marking a number called. This was easy with numpy, I stopped in the middle to make sure the numbers were converted to integers.
- 4:44 -> 5:09: defining checking if a board has been won. Easy with numpy, especially without diagonals.
- 5:09 -> 5:21: defining the sum of the unmarked. very easy.
- 5:21 -> 5:49: testing. fixed 5 small bugs and got the right answer for the test case. (bugs: forgot import, forgot to run with poetry, forgot enumerate, wrong name for variable)
- 5:49 -> 5:56: ran the real data and got the right answer
- 5:56 -> 6:07: context switch + reading part 2
- 6:07 -> 6:25: housekeeping (copying + renaming function, adding invocation) and reading
- 6:25 -> 7:36: start editing in earnest. I had all the functions I needed, just needed a different logic in the main function to keep track of the last won board. I went down a false trail here with a dict. Also my girlfriend wanted my attention in the middle for a minute; I paused the timer.
- 7:36 -> 7:48: test with the text case. fix 1 bug (typo)
- 7:48 -> 7:54: run with the real thing
- 7:55: submit answer. Success!
I threw together a quick plot of this. Definitely room for improvement in typing!
import itertools as it
import numpy as np
INPUT = """
7,4,9,5,11,17,23,2,0,14,21,24,10,16,13,6,15,25,12,22,18,20,8,19,3,26,1
22 13 17 11 0
8 2 23 4 24
21 9 14 16 7
6 10 3 18 5
1 12 20 15 19
3 15 0 2 22
9 18 13 17 5
19 8 7 25 23
20 11 10 24 4
14 21 16 12 6
14 21 17 24 4
10 16 15 9 19
18 8 23 26 20
22 11 13 6 5
2 0 12 3 7
"""
INPUT = open('input.txt').read()
def make_boards(lines):
boards = []
for i in lines:
board = np.zeros((5, 5), dtype=int)
called = np.zeros((5, 5), dtype=bool)
for ii, line in enumerate(i.splitlines()):
for j, val in enumerate(line.split()):
board[ii][j] = int(val)
boards.append((board, called))
return boards
def marknumber(board, number):
board, called = board
called[np.where(board == number)] = True
def checkwin(board):
board, called = board
return np.any(np.all(called, axis=0)) or np.any(np.all(called, axis=1))
def get_sum_unmarked(board):
board, called = board
return np.sum(board[called == False])
def play_bingo(input):
stuff = input.split('\n\n')
numbers = stuff[0]
boards = make_boards(stuff[1:])
for n in map(int, numbers.strip().split(',')):
for board in boards:
marknumber(board, n)
haswon = checkwin(board)
if haswon:
s = get_sum_unmarked(board)
return s * n
def play_bingov2(input):
stuff = input.split('\n\n')
numbers = stuff[0]
boards = make_boards(stuff[1:])
winningscores = []
skipboards = []
for n in map(int, numbers.strip().split(',')):
for i, board in enumerate(boards):
marknumber(board, n)
haswon = checkwin(board)
if haswon and not i in skipboards:
s = get_sum_unmarked(board)
winningscores.append(s * n)
skipboards.append(i)
return winningscores[-1]
if __name__ == '__main__':
print('part 1: ', play_bingo(INPUT))
print('part 2: ', play_bingov2(INPUT))