92 lines
2.7 KiB
Markdown
92 lines
2.7 KiB
Markdown
---
|
|
title: "AoC 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 neighbors 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 challenge 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 neighbors. 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
|
|
```
|