--- title: "Advent of Code 2021 - Day 11" date: 2021-12-11T09:09:14+01:00 math: true day: 11 stars: 2 --- Today's challenge is available [here](https://adventofcode.com/2021/day/11). Octopuses flash when their energy levels reach above 9 and it causes their neignbours to increase their levels by 1 too? This sounds like a perfect job for some kind of a matrix operation... Loading data: ```py def load(): with open('../.input/day11') as f: lines = list(map(str.strip, f.readlines())) return np.array([int(num) for line in lines for num in line]).reshape((-1, len(lines))) ``` In this chanllenge I decided to try my hand at using convolution matrix to solve this problem. First I had to create a function that would return an array containing octopuses and their neighbours. For this I used the following code: ```py from numpy.lib.stride_tricks import as_strided def windows(target, shape=(3, 3), stride: int = 1): target = np.pad(target, 1, 'constant', constant_values=0) (t_h, t_w), (w_h, w_w) = target.shape, shape out_shape = ( 1 + (t_h - w_h) // stride, 1 + (t_w - w_w) // stride, w_h, w_w ) out_strides = ( target.strides[0] * stride, target.strides[1] * stride, target.strides[0], target.strides[1] ) return as_strided(target, shape=out_shape, strides=out_strides) ``` The function `as_strided` is a pretty esoteric function, which operates directly on memory, which also means a lot can go very wrong when using it. Here I used this function to calculate how the energy levels would progress through the time: ```py def step(input: np.ndarray): current = input + np.ones(input.shape, dtype=np.int32) flashed = np.zeros(input.shape, dtype=np.bool8) while np.any(now_flashed := ((current * ~flashed) > 9)): convolution = np.tensordot( windows(now_flashed.astype(np.int32)), np.array([[1, 1, 1], [1, 0, 1], [1, 1, 1]]), axes=((2, 3), (0, 1)) ) flashed |= now_flashed current = current + convolution * ~flashed return np.where(current > 9, 0, current), flashed ``` I used a convolution matrix of the following shape: $$ \begin{bmatrix} 1 & 1 & 1 \\\\ 1 & 0 & 1 \\\\ 1 & 1 & 1 \end{bmatrix} $$ ## Task 1 Here we sum all the flashes that happened in all of the iterations with `np.sum`: ```py def solve1() -> int: input = load() flash_count = 0 for _ in range(100): input, flashed = step(input) flash_count += np.sum(flashed) return flash_count ``` ## Task 2 Here we check when all the octopuses flashed with `np.all`: ```py def solve2() -> int: input = load() steps = 0 while (steps := steps + 1): input, flashed = step(input) if np.all(flashed): return steps ```