Render individual aoc pages
This commit is contained in:
parent
76290df734
commit
2061c1e8b5
36
src/content/aoc/2021/01.md
Normal file
36
src/content/aoc/2021/01.md
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
---
|
||||||
|
title: "Advent of Code 2021 - Day 1"
|
||||||
|
date: 2021-12-01T23:25:56+01:00
|
||||||
|
year: 2021
|
||||||
|
day: 1
|
||||||
|
stars: 2
|
||||||
|
---
|
||||||
|
|
||||||
|
First day of the Advent of Code turned out to be pretty easy, the first task was to find the number of consecutive increases in the sequence of numbers. The simplest way to do it would be to use a simple loop to compare each number with the one before it.
|
||||||
|
|
||||||
|
## Task 1
|
||||||
|
|
||||||
|
In my solution however I took a different approach - I sliced the sequence, so that there's two of them and they're offset by 1. I then used the `zip` function to zip the values together into tuples of the form `(previous, current)`. Then I used a generator to compare the values in tuple, which outputs True if `current` is greater than `previous` and False otherwise. Then I summed the resulting sequence of booleans... You can do that? In Python yes, because `True` is 1 and `False` is 0.
|
||||||
|
|
||||||
|
```py
|
||||||
|
def solve1() -> int:
|
||||||
|
with open('../.input/day01', 'r') as f:
|
||||||
|
numbers: list[int] = [int(line) for line in f.readlines()]
|
||||||
|
return sum((n > p) for p, n in zip(numbers[:-1], numbers[1:]))
|
||||||
|
```
|
||||||
|
|
||||||
|
## Task 2
|
||||||
|
|
||||||
|
In the second task I used the same approach, but this time I zipped 3 consecutive elements together to form a window tuple. Then I zipped another 3, but this time offset by 1. Then I zipped both of them together and used the `sum` function to sum the values of the tuples. The rest is analogous to the first task.
|
||||||
|
|
||||||
|
```py
|
||||||
|
def solve2() -> int:
|
||||||
|
with open('../.input/day01', 'r') as f:
|
||||||
|
numbers: list[int] = [int(line) for line in f.readlines()]
|
||||||
|
return sum((sum(n) > sum(p)) for p, n in zip(
|
||||||
|
zip(numbers[:-3], numbers[1:-2], numbers[2:-1]),
|
||||||
|
zip(numbers[1:-2], numbers[2:-1], numbers[3:])
|
||||||
|
))
|
||||||
|
```
|
||||||
|
|
||||||
|
Overall this task was pretty easy, but the second task took me a bit longer, I didn't really get what I was supposed to do at first :smile:
|
83
src/content/aoc/2021/02.md
Normal file
83
src/content/aoc/2021/02.md
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
---
|
||||||
|
title: "Advent of Code 2021 - Day 2"
|
||||||
|
date: 2021-12-02T19:46:19+01:00
|
||||||
|
year: 2021
|
||||||
|
day: 2
|
||||||
|
stars: 2
|
||||||
|
---
|
||||||
|
|
||||||
|
Day 2 of Advent of Code is [here,](https://adventofcode.com/2021/day/2) and it turned out to be pretty easy. Starting off all we get is a list of instructions of the form `<instruction> <parameter>` where `<instruction>` is one of `forward`, `up` or `down` and `<parameter>` is an integer.
|
||||||
|
|
||||||
|
My idea to parse this list of instructions was to create a list of tuples of the form `(instruction, parameter)` using a regular expression `^([a-z]+) ([0-9]+)$`. This regular expression makes use of [capturing groups](https://docs.python.org/3/howto/regex.html#grouping) to extract the instruction and the parameter separately. The groups are accessed using `match.group(1)` and `match.group(2)`.
|
||||||
|
|
||||||
|
```python
|
||||||
|
pattern = re.compile('^([a-z]+) ([0-9]+)$')
|
||||||
|
|
||||||
|
def load_input() -> list[Tuple[str, int]]:
|
||||||
|
with open('../.input/day02', 'r') as f:
|
||||||
|
return [
|
||||||
|
(match.group(1), int(match.group(2))) for match
|
||||||
|
in (pattern.search(line.strip()) for line in f.readlines())
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Task 1
|
||||||
|
|
||||||
|
Here my idea was to use a dictionary to store functions (lambdas) under keys `'forward'`, `'up'` and `'down'`. The functions are then called with the parameter as argument together with the current position.
|
||||||
|
|
||||||
|
I used `functools.reduce` to fold the list of instructions into a single final position. Reduce represents in Python the mathematical concept of a [fold](https://en.wikipedia.org/wiki/Fold_(higher-order_function)), which also is a commonly used operation in functional programming. The function `functools.reduce` takes a function, a list and an initial value as arguments. The function is applied to the initial value and the first element of the list, then the result is used as the initial value for the next iteration. The result of the last iteration is returned.
|
||||||
|
|
||||||
|
This operation can be represented as `foldl f z xs` in some other functional languages:
|
||||||
|
|
||||||
|
```haskell
|
||||||
|
-- Haskell
|
||||||
|
foldl f z [] = z
|
||||||
|
foldl f z (x:xs) = foldl f (f z x) xs
|
||||||
|
```
|
||||||
|
|
||||||
|
Here's my solution:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def solve1() -> int:
|
||||||
|
numbers = load_input()
|
||||||
|
instructions = {
|
||||||
|
"forward": lambda arg, x, y: (x + arg, y),
|
||||||
|
"up": lambda arg, x, y: (x, y + arg),
|
||||||
|
"down": lambda arg, x, y: (x, y - arg),
|
||||||
|
}
|
||||||
|
|
||||||
|
x, y = reduce(lambda pos, item: instructions[item[0]](item[1], *pos), numbers, (0, 0))
|
||||||
|
return abs(x) * abs(y)
|
||||||
|
```
|
||||||
|
|
||||||
|
The key part is right here:
|
||||||
|
```python
|
||||||
|
# unpacking the final position into x and y
|
||||||
|
x, y = reduce(
|
||||||
|
# Function to apply which takes the previous result (accumulator) and the next item in the list.
|
||||||
|
# We use dict to get the function from the dictionary.
|
||||||
|
# We use *pos to unpack accumulated value into function arguments.
|
||||||
|
lambda pos, item: instructions[item[0]](item[1], *pos),
|
||||||
|
# list of instructions to fold
|
||||||
|
numbers,
|
||||||
|
# initial value for the accumulator
|
||||||
|
(0, 0)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Task 2
|
||||||
|
|
||||||
|
Task 2 was pretty much identical to task 1. I used a dictionary to store functions (lambdas) under keys `'forward'`, `'up'` and `'down'`, except that the functions have different bodies and they take an additional parameter `a` for aim.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def solve2() -> int:
|
||||||
|
numbers = load_input()
|
||||||
|
instructions = {
|
||||||
|
"forward": lambda arg, x, y, a: (x + arg, y + a * arg, a),
|
||||||
|
"up": lambda arg, x, y, a: (x, y, a - arg),
|
||||||
|
"down": lambda arg, x, y, a: (x, y, a + arg),
|
||||||
|
}
|
||||||
|
|
||||||
|
x, y, _ = reduce(lambda pos, item: instructions[item[0]](item[1], *pos), numbers, (0, 0, 0))
|
||||||
|
return abs(x) * abs(y)
|
||||||
|
```
|
101
src/content/aoc/2021/03.md
Normal file
101
src/content/aoc/2021/03.md
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
---
|
||||||
|
title: "Advent of Code 2021 - Day 3"
|
||||||
|
date: 2021-12-03T16:51:41+01:00
|
||||||
|
year: 2021
|
||||||
|
day: 3
|
||||||
|
stars: 2
|
||||||
|
---
|
||||||
|
|
||||||
|
Today's challenge is [here](https://adventofcode.com/2021/day/3). It turned out to be pretty tricky with many possible approaches to solve it. First task was pretty trivial, but I kind of feel like my second solution was a bit overengineered :smile:
|
||||||
|
|
||||||
|
Loading data is pretty simple, all I had to do here was to load strings:
|
||||||
|
```python
|
||||||
|
def load_input() -> list[str]:
|
||||||
|
with open('../.input/day03', 'r') as f:
|
||||||
|
return [line.strip() for line in f.readlines()]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Task 1
|
||||||
|
Here we had to find two binary numbers based on the frequency of digits at each position. I used a `dict` to store count of `0`s and `1`s at each position for all binary numbers. Then I used this `dict` to construct two binary numbers. The number for gamma consists of the most frequent digits at each position, and the number for epsilon consists of the least frequent digits at each position.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def solve1() -> int:
|
||||||
|
input = load_input()
|
||||||
|
cache = [{"0": 0, "1": 0} for _ in range(len(input[0]))]
|
||||||
|
|
||||||
|
for binary in input:
|
||||||
|
for i, c in enumerate(binary):
|
||||||
|
cache[i][c] += 1
|
||||||
|
|
||||||
|
gamma, epsilon = map(sum, zip(*[
|
||||||
|
(2**i, 0) if c["1"] > c["0"] else (0, 2**i)
|
||||||
|
for i, c in enumerate(reversed(cache))
|
||||||
|
]))
|
||||||
|
return gamma * epsilon
|
||||||
|
```
|
||||||
|
|
||||||
|
After finding the binary numbers I converted both of them to decimal and multiplied them. An interesting part here is the usage of `zip(*iterable])` notation to turn a list of tuples into a tuple of lists.
|
||||||
|
|
||||||
|
It can be visualized as:
|
||||||
|
`[(x1, y1), (x2, y2), ...] -> [[x1, x2, ...], [y1, y2, ...]]`
|
||||||
|
|
||||||
|
## Task 2
|
||||||
|
|
||||||
|
Here we had to find a single remaining binary number for each case - oxygen and carbon. I used a `dict` once again to store the frequency of each digit at each position. Then I used this data to filter out the binary numbers until there is only one left. The part where I filter the numbers is also where I update the dict by subtracting the frequency, so that it stays up to date for the next iteration.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def find_single(input: list[str], reverse: bool = False) -> int:
|
||||||
|
cache = [{"0": 0, "1": 0} for _ in range(len(input[0]))]
|
||||||
|
remaining = input[:]
|
||||||
|
|
||||||
|
for binary in remaining:
|
||||||
|
for i, digit in enumerate(binary):
|
||||||
|
cache[i][digit] += 1
|
||||||
|
|
||||||
|
for i, c in enumerate(cache):
|
||||||
|
if len(remaining) == 1:
|
||||||
|
break
|
||||||
|
|
||||||
|
generator = (entry for entry in remaining)
|
||||||
|
remaining = []
|
||||||
|
winning_digit = ("0" if c["0"] <= c["1"] else "1") if reverse else ("1" if c["1"] >= c["0"] else "0")
|
||||||
|
for binary in generator:
|
||||||
|
if binary[i] == winning_digit:
|
||||||
|
remaining.append(binary)
|
||||||
|
else:
|
||||||
|
for digit, cache_dict in zip(binary, cache):
|
||||||
|
cache_dict[digit] -= 1
|
||||||
|
|
||||||
|
return remaining[0]
|
||||||
|
```
|
||||||
|
|
||||||
|
Here I use the function to find the multiply from the resulting numbers for oxygen and cargon:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def solve2() -> int:
|
||||||
|
input = load_input()
|
||||||
|
oxygen, carbon = input[:], input[:]
|
||||||
|
res_o = sum(2**int(i) for i, x in enumerate(reversed(find_single(oxygen, reverse=False))) if x == "1")
|
||||||
|
res_c = sum(2**int(i) for i, x in enumerate(reversed(find_single(carbon, reverse=True))) if x == "1")
|
||||||
|
return res_o * res_c
|
||||||
|
```
|
||||||
|
|
||||||
|
## Bonus: Task 1 in NumPy
|
||||||
|
The way this works is it loads all data into a single matrix, calculates mean value for each place, converts to nearest integer. The other thing is the `logical_not` of that. Then you get decimal number by calculating dot product of the binary number and a `[1, 2, 4, 8, ...]` vector reversed.
|
||||||
|
|
||||||
|
```python
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
|
def load_input_np() -> np.ndarray:
|
||||||
|
with open('../.input/day03', 'r') as f:
|
||||||
|
return np.array([list(map(np.int32, list(i.strip()))) for i in f.readlines()])
|
||||||
|
|
||||||
|
|
||||||
|
def solve1_np() -> int:
|
||||||
|
input = load_input_np()
|
||||||
|
binary = input.mean(axis=0) >= 0.5
|
||||||
|
invert = ~binary
|
||||||
|
decimalize = 2 ** np.arange(binary.shape[0])[::-1]
|
||||||
|
return sum(binary * decimalize) * sum(invert * decimalize)
|
||||||
|
```
|
58
src/content/aoc/2021/04.md
Normal file
58
src/content/aoc/2021/04.md
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
---
|
||||||
|
title: "Advent of Code 2021 - Day 4"
|
||||||
|
date: 2021-12-04T11:23:18+01:00
|
||||||
|
year: 2021
|
||||||
|
day: 4
|
||||||
|
stars: 2
|
||||||
|
math: true
|
||||||
|
---
|
||||||
|
|
||||||
|
Today's problem is [Day 4](https://adventofcode.com/2021/day/4). This time the challenge is inspied by the game called Bingo. We get a vector of number and a number of bingo boards. We need to find the board that wins first and last, then we need to calculate score for it.
|
||||||
|
|
||||||
|
Loading data from file into NumPy arrays:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def loader_np() -> np.ndarray:
|
||||||
|
with open('../.input/day04', 'r') as f:
|
||||||
|
data = f.read().splitlines()
|
||||||
|
|
||||||
|
vector = np.array([int(x) for x in data[0].split(",")])
|
||||||
|
tensor = np.array([
|
||||||
|
np.array(list(map(np.int32, map(str.split, board))))
|
||||||
|
for board
|
||||||
|
in [data[i:i+5] for i in range(2, len(data), 6)]
|
||||||
|
])
|
||||||
|
return vector, tensor
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Task 1
|
||||||
|
Here we need to find the first winning board. I used the `np.isin` function to find the mask for all board, for example given vector `[1, 2, 3, 4, 5]` and matrix `[[1, 2, 3], [4, 5, 6]]` we get `[[True, True, True], [True, True, False]]`. The vector changes size through slicing, with each iteration of the outer loop - it simulates drawing a new number. The inner for loop iterates through all boards (matrices), there we check if on any axis there's a full row or column of matching numbers. If there's a match we calculate the score.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def solve1() -> int:
|
||||||
|
vector, tensor = loader_np()
|
||||||
|
for scale in range(1, len(vector)+1):
|
||||||
|
for matrix in tensor:
|
||||||
|
mask = np.isin(matrix, vector[:scale])
|
||||||
|
if mask.all(axis=0).any() or mask.all(axis=1).any():
|
||||||
|
return (matrix * ~mask).sum() * vector[scale-1]
|
||||||
|
```
|
||||||
|
|
||||||
|
The score is calculated by taking a dot product of the matrix with the negation of the mask (which eliminates unmarked numbers), summing the remaining values and multiplying by the value of the drawn number.
|
||||||
|
|
||||||
|
$$ \text{score} = v_{drawn} \cdot \sum_{i=1}^{n} \sum_{j=1}^{m} \text{board}[i,j] \cdot \neg \text{mask}[i,j] $$
|
||||||
|
|
||||||
|
## Task 2
|
||||||
|
This is analogous to the first task, but we need to find the last winning board. I iterate from the end of the vector. The inner loop is similar to the one in the first task, just needed to find when the first board doesn't match and then calculate another mask for the round afterwards (scale+1).
|
||||||
|
|
||||||
|
```python
|
||||||
|
def solve2() -> int:
|
||||||
|
vector, tensor = loader_np()
|
||||||
|
for scale in range(len(vector)+1, 0, -1):
|
||||||
|
for matrix in tensor:
|
||||||
|
mask = np.isin(matrix, vector[:scale])
|
||||||
|
if not mask.all(axis=0).any() and not mask.all(axis=1).any():
|
||||||
|
prev_mask = np.isin(matrix, vector[:scale+1])
|
||||||
|
return (matrix * ~prev_mask).sum() * vector[scale]
|
||||||
|
```
|
70
src/content/aoc/2021/05.md
Normal file
70
src/content/aoc/2021/05.md
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
---
|
||||||
|
title: "Advent of Code 2021 - Day 5"
|
||||||
|
date: 2021-12-05T13:51:01+01:00
|
||||||
|
year: 2021
|
||||||
|
day: 5
|
||||||
|
stars: 2
|
||||||
|
---
|
||||||
|
|
||||||
|
Today's challenge is [here](https://adventofcode.com/2021/day/5). Here we get a punch of lines in the form of two points and we have to find number of points where they cross. For parsing the data I used a regular expression `(\d+),(\d+) -> (\d+),(\d+)`.
|
||||||
|
|
||||||
|
Loading data from the input file:
|
||||||
|
```python
|
||||||
|
pattern = re.compile(r'(\d+),(\d+) -> (\d+),(\d+)')
|
||||||
|
|
||||||
|
|
||||||
|
def loader() -> list[Tuple[Tuple[int, int], Tuple[int, int]]]:
|
||||||
|
with open('../.input/day05', 'r') as f:
|
||||||
|
return [
|
||||||
|
((int(x1), int(y1)), (int(x2), int(y2)))
|
||||||
|
for x1, y1, x2, y2
|
||||||
|
in pattern.findall(f.read())
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Task 1
|
||||||
|
In this task we have to find number of points where lines cross, but we have to skip the diagonal lines. When I was solving this task I did a mistake and I included the logic for counting the diagonal lines as well.
|
||||||
|
|
||||||
|
Here is the function for generating the list of points for any line. I used generators pretty liberally as well as the `itertools.repeat` function, which is used to generate infinite sequences of a single value. I had to use that for cases where the line is vertical or horizontal, because otherwise the zip function would stop at the first iteration.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def find_points_in_line(x1, y1, x2, y2, skip_diagonal: bool = False) -> Iterable[Tuple[int, int]]:
|
||||||
|
if skip_diagonal and x1 != x2 and y1 != y2:
|
||||||
|
return []
|
||||||
|
if x2 < x1 and y2 < y1:
|
||||||
|
return find_points_in_line(x2, y2, x1, y1)
|
||||||
|
if x2 < x1:
|
||||||
|
y = (y for y in range(y1, y2 + 1)) if y1 != y2 else repeat(y1)
|
||||||
|
return zip(range(x1, x2 - 1, -1), y)
|
||||||
|
if y2 < y1:
|
||||||
|
x = (x for x in range(x1, x2 + 1)) if x1 != x2 else repeat(x1)
|
||||||
|
return zip(x, range(y1, y2 - 1, -1))
|
||||||
|
if x1 == x2:
|
||||||
|
return zip(repeat(x1), range(min(y1, y2), max(y1, y2) + 1))
|
||||||
|
if y1 == y2:
|
||||||
|
return zip(range(min(x1, x2), max(x1, x2) + 1), repeat(y1))
|
||||||
|
return zip(range(x1, x2 + 1), range(y1, y2 + 1))
|
||||||
|
```
|
||||||
|
|
||||||
|
Here we use two sets to store the visited points as well as the points where lines cross. We can do this because we don't need to know how many lines cross at any given point, just the number of crossing points in general. A set is a perfect choice for this, it saves us memory and time.
|
||||||
|
```python
|
||||||
|
def solve1(skip_diagonal=True) -> int:
|
||||||
|
visited_pts = set()
|
||||||
|
crossed_pts = set()
|
||||||
|
for (start, end) in loader():
|
||||||
|
for x, y in find_points_in_line(*start, *end, skip_diagonal):
|
||||||
|
if (x, y) in visited_pts:
|
||||||
|
crossed_pts.add((x, y))
|
||||||
|
else:
|
||||||
|
visited_pts.add((x, y))
|
||||||
|
return len(crossed_pts)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Task 2
|
||||||
|
|
||||||
|
Do I need to say anything here? :smile:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def solve2() -> int:
|
||||||
|
return solve1(skip_diagonal=False)
|
||||||
|
```
|
72
src/content/aoc/2021/06.md
Normal file
72
src/content/aoc/2021/06.md
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
---
|
||||||
|
title: "Advent of Code 2021 - Day 6"
|
||||||
|
date: 2021-12-06T19:40:20+01:00
|
||||||
|
year: 2021
|
||||||
|
day: 6
|
||||||
|
stars: 2
|
||||||
|
---
|
||||||
|
|
||||||
|
Today's challenge is [here](https://adventofcode.com/2021/day/6). We have to find the amount of fish 'split' after some days. A problem based on [exponential growth](https://en.wikipedia.org/wiki/Exponential_growth).
|
||||||
|
|
||||||
|
When I started doing this challenge I first thought about using Object-Oriented Programming, but that didn't work out. When calculating large amounts of fish I started to run out of memory and the algorithm was too slow.
|
||||||
|
|
||||||
|
Loading the data:
|
||||||
|
```python
|
||||||
|
def load() -> list[int]:
|
||||||
|
with open('../.input/day06') as f:
|
||||||
|
return [int(line) for line in f.read().split(",")]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Task 1
|
||||||
|
|
||||||
|
Pretty simple, we just need to advance the fish and then count the amount of fish. I created simple methods in the `LanternFish` class that do just that.
|
||||||
|
|
||||||
|
Here's a class that represents a fish:
|
||||||
|
```python
|
||||||
|
class LanternFish:
|
||||||
|
def __init__(self, cycle: int, current: int = None):
|
||||||
|
self.cycle = cycle
|
||||||
|
self.current = current or cycle
|
||||||
|
self.children = []
|
||||||
|
|
||||||
|
def advance(self) -> None:
|
||||||
|
self.current -= 1
|
||||||
|
[child.advance() for child in self.children]
|
||||||
|
if self.current == -1:
|
||||||
|
self.current = self.cycle
|
||||||
|
self.children.append(LanternFish(cycle=6, current=8))
|
||||||
|
|
||||||
|
def count(self) -> int:
|
||||||
|
return 1 + sum([child.count() for child in self.children])
|
||||||
|
```
|
||||||
|
|
||||||
|
Here we iterate through number of days and advance the fish. The way fishes are related to each other is that they all form a tree. When I advance a fish, I also advance all of its children. The amount of fish is the sum of the amount of fish in the tree.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def solve1() -> int:
|
||||||
|
fishes = [LanternFish(cycle=6, current=init) for init in load()]
|
||||||
|
[[fish.advance() for fish in fishes] for _ in range(80)]
|
||||||
|
return sum([fish.count() for fish in fishes])
|
||||||
|
```
|
||||||
|
|
||||||
|
## Task 2
|
||||||
|
|
||||||
|
Here I took a different approach, I stored the fishes in a simple dictionary. The key is the day and the value is the number of fishes. I then iterate through the days and advance the fishes.
|
||||||
|
|
||||||
|
The way this works is kind of hard to explain really, but the key to it all is the `%` [modulo operator](https://en.wikipedia.org/wiki/Modulo_operation). The modulo operator returns the remainder of a division. In this case, the modulo operator is used to determine whether some fish should give birth.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def solve2() -> int:
|
||||||
|
fishes = { i: 0 for i in range(7) }
|
||||||
|
newborns = { i: 0 for i in range(9) }
|
||||||
|
|
||||||
|
for init in load():
|
||||||
|
fishes[init] += 1
|
||||||
|
|
||||||
|
for epoch in range(256):
|
||||||
|
born_from_old = fishes[epoch % 7]
|
||||||
|
fishes[epoch % 7] += newborns[epoch % 9]
|
||||||
|
newborns[epoch % 9] += born_from_old
|
||||||
|
|
||||||
|
return sum(fishes.values()) + sum(newborns.values())
|
||||||
|
```
|
73
src/content/aoc/2021/07.md
Normal file
73
src/content/aoc/2021/07.md
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
---
|
||||||
|
title: "Advent of Code 2021 - Day 7"
|
||||||
|
date: 2021-12-07T22:07:00+01:00
|
||||||
|
year: 2021
|
||||||
|
math: true
|
||||||
|
day: 7
|
||||||
|
stars: 2
|
||||||
|
---
|
||||||
|
|
||||||
|
Today's challenge is [here](https://adventofcode.com/2021/day/7). We have to find the optimal point for all crabs to move to on a single axis. The optimal point is the point where the fuel expenditure to all other points is minimal.
|
||||||
|
|
||||||
|
Loading the input data:
|
||||||
|
```python
|
||||||
|
def load() -> list[int]:
|
||||||
|
with open('../.input/day07') as f:
|
||||||
|
return [int(x) for x in f.readline().split(',')]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Task 1
|
||||||
|
In this task the way to calculate the fuel expenditure is to sum up the fuel expenditure to all other points. The way it's calculated is linear. Formula for fuel expenditure, where C is the set of all points and x is the point we want to calculate the fuel expenditure for:
|
||||||
|
$$\sum_{c \in C} |x - c|$$
|
||||||
|
|
||||||
|
We are looking for the point with minimal fuel expenditure:
|
||||||
|
$$\min_{x} \sum_{c \in C} |x - c|$$
|
||||||
|
|
||||||
|
The way to find the optimal point is to find the median of all the points where crabs are placed, because the median happens to be the point where the fuel expenditure is minimal. Formula for the median:
|
||||||
|
$$\frac{1}{2} \sum_{c \in C} c$$
|
||||||
|
|
||||||
|
I don't really know how this works myself, but the proof for why it works can be found [here](https://math.stackexchange.com/questions/113270/the-median-minimizes-the-sum-of-absolute-deviations-the-ell-1-norm).
|
||||||
|
|
||||||
|
Here's the code for the task:
|
||||||
|
```python
|
||||||
|
def solve1() -> int:
|
||||||
|
numbers = load()
|
||||||
|
median = sorted(numbers)[len(numbers) // 2]
|
||||||
|
return sum(abs(x - median) for x in numbers)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Task 2
|
||||||
|
In this task we have to find the optimal point for all crabs to move to on a single axis, similar to task 1. The difference is that the way fuel expenditure is caluculated is a sequence: 1, 1+2, 1+2+3, 1+2+3+4, ...
|
||||||
|
|
||||||
|
The way I solved this is I looked up the formula for the function that calculates the value for the n-th element in the sequence on the Internet. It happens to be:
|
||||||
|
|
||||||
|
$$ f(n) = \frac{n * (n + 1)}{2} $$
|
||||||
|
|
||||||
|
I implemented this formula in the code below:
|
||||||
|
```python
|
||||||
|
def nth_sum(n: int) -> int:
|
||||||
|
return n * (n + 1) // 2
|
||||||
|
```
|
||||||
|
|
||||||
|
In the solution to the task 2 I used NumPy to create a matrix of the form:
|
||||||
|
$$ \begin{bmatrix} 0 & 0 & 0 & \cdots & 0 \\\\ 1 & 1 & 1 & \cdots & 1 \\\\ 2 & 2 & 2 & \cdots & 2 \\\\ \vdots & \vdots & \vdots & \ddots & \vdots \\\\ n & n & n & \cdots & n \end{bmatrix} $$
|
||||||
|
|
||||||
|
Then I subtracted the vector of the crab positions from the matrix. The result is a matrix with the distances from the crab to all other possible points.
|
||||||
|
|
||||||
|
$$ \begin{bmatrix} |0 - c_1| & |0 - c_2| & |0 - c_3| & \cdots & |0 - c_i| \\\\ |1 - c_1| & |1 - c_2| & |1 - c_3| & \cdots & |1 - c_i| \\\\ |2 - c_1| & |2 - c_2| & |2 - c_3| & \cdots & |2 - c_i| \\\\ \vdots & \vdots & \vdots & \ddots & \vdots \\\\ |n - c_1| & |n - c_2| & |n - c_3| & \cdots & |n - c_i| \end{bmatrix} $$
|
||||||
|
|
||||||
|
We then apply the formula for the n-th element in the sequence to the matrix. The result is a matrix with the fuel expenditure to all other points. When we sum the values in rows of the matrix we get the total fuel expenditure to all points for the crabs.
|
||||||
|
|
||||||
|
$$ \min \sum_{c \in C} f(|x - c|) $$
|
||||||
|
|
||||||
|
By finding the row with the minimal fuel expenditure we can find the optimal point. Below is the implementation in NumPy:
|
||||||
|
```python
|
||||||
|
def solve2() -> int:
|
||||||
|
numbers = np.array(load())
|
||||||
|
search_vector = np.arange(0, max(numbers) + 1)
|
||||||
|
search_matrix = np.tile(search_vector, (len(numbers), 1)).T
|
||||||
|
distance = abs(search_matrix - numbers)
|
||||||
|
fuel_expended = nth_sum(distance)
|
||||||
|
return min(fuel_expended.sum(axis=1))
|
||||||
|
```
|
||||||
|
|
122
src/content/aoc/2021/08.md
Normal file
122
src/content/aoc/2021/08.md
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
---
|
||||||
|
title: "Advent of Code 2021 - Day 8"
|
||||||
|
date: 2021-12-14T23:42:27+01:00
|
||||||
|
year: 2021
|
||||||
|
day: 8
|
||||||
|
stars: 2
|
||||||
|
---
|
||||||
|
|
||||||
|
```python
|
||||||
|
from functools import reduce
|
||||||
|
|
||||||
|
|
||||||
|
def load() -> list[tuple[list[str], ...]]:
|
||||||
|
with open('../.input/day08', 'r') as f:
|
||||||
|
return [tuple(map(str.split, line.split('|'))) for line in f.read().splitlines()]
|
||||||
|
|
||||||
|
|
||||||
|
def solve1() -> int:
|
||||||
|
return sum(
|
||||||
|
len(list(filter(lambda x: len(x) in [2, 3, 4, 7], output)))
|
||||||
|
for _, output in load()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
A = 0b0000001
|
||||||
|
B = 0b0000010
|
||||||
|
C = 0b0000100
|
||||||
|
D = 0b0001000
|
||||||
|
E = 0b0010000
|
||||||
|
F = 0b0100000
|
||||||
|
G = 0b1000000
|
||||||
|
|
||||||
|
n_to_s = {
|
||||||
|
0: A | B | C | E | F | G,
|
||||||
|
1: C | F,
|
||||||
|
2: A | C | D | E | G,
|
||||||
|
3: A | C | D | F | G,
|
||||||
|
4: B | C | D | F,
|
||||||
|
5: A | B | D | F | G,
|
||||||
|
6: A | B | D | E | F | G,
|
||||||
|
7: A | C | F,
|
||||||
|
8: A | B | C | D | E | F | G,
|
||||||
|
9: A | B | C | D | F | G,
|
||||||
|
}
|
||||||
|
|
||||||
|
s_to_n = {
|
||||||
|
A | B | C | E | F | G: 0,
|
||||||
|
C | F: 1,
|
||||||
|
A | C | D | E | G: 2,
|
||||||
|
A | C | D | F | G: 3,
|
||||||
|
B | C | D | F: 4,
|
||||||
|
A | B | D | F | G: 5,
|
||||||
|
A | B | D | E | F | G: 6,
|
||||||
|
A | C | F: 7,
|
||||||
|
A | B | C | D | E | F | G: 8,
|
||||||
|
A | B | C | D | F | G: 9,
|
||||||
|
}
|
||||||
|
|
||||||
|
possible = {
|
||||||
|
2: n_to_s[1],
|
||||||
|
3: n_to_s[7],
|
||||||
|
4: n_to_s[4],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def negate(string: str):
|
||||||
|
return list(filter(lambda x: x not in string, "abcdefg"))
|
||||||
|
|
||||||
|
|
||||||
|
def check_pattern(pattern: list[str], mapping: dict[str, int]):
|
||||||
|
def check_legal(_str: str):
|
||||||
|
return reduce(lambda a, c: a | mapping[c], _str, 0) in s_to_n
|
||||||
|
return all(map(check_legal, pattern))
|
||||||
|
|
||||||
|
|
||||||
|
def find_mapping(pattern: list[str]):
|
||||||
|
domains = {
|
||||||
|
char: 0b1111111
|
||||||
|
for char in 'abcdefg'
|
||||||
|
}
|
||||||
|
|
||||||
|
# remove obvious impossible choices
|
||||||
|
for activation in pattern:
|
||||||
|
length = len(activation)
|
||||||
|
if length in possible:
|
||||||
|
for char in activation:
|
||||||
|
domains[char] &= possible[length]
|
||||||
|
for char in negate(activation):
|
||||||
|
domains[char] &= ~possible[length]
|
||||||
|
|
||||||
|
# bruteforce search
|
||||||
|
search: list[dict[str, int]] = [{}]
|
||||||
|
for char, domain in domains.items():
|
||||||
|
deeper = []
|
||||||
|
for i in range(7):
|
||||||
|
if domain & (1 << i):
|
||||||
|
for s in search:
|
||||||
|
new = s.copy()
|
||||||
|
new[char] = (1 << i)
|
||||||
|
deeper.append(new)
|
||||||
|
search = deeper
|
||||||
|
|
||||||
|
for mapping in search:
|
||||||
|
if check_pattern(pattern, mapping):
|
||||||
|
return mapping
|
||||||
|
|
||||||
|
|
||||||
|
def string_to_int(string: str, mapping: dict) -> int:
|
||||||
|
return s_to_n[reduce(lambda a, c: a | mapping[c], string, 0)]
|
||||||
|
|
||||||
|
|
||||||
|
def solve2() -> int:
|
||||||
|
return sum(
|
||||||
|
reduce(lambda a, c: a * 10 + string_to_int(c, find_mapping(pattern)), output, 0)
|
||||||
|
for pattern, output in load()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print(solve1()) # 301
|
||||||
|
print(solve2()) # 908067
|
||||||
|
```
|
62
src/content/aoc/2021/09.md
Normal file
62
src/content/aoc/2021/09.md
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
---
|
||||||
|
title: "Advent of Code 2021 - Day 9"
|
||||||
|
date: 2021-12-14T23:42:35+01:00
|
||||||
|
year: 2021
|
||||||
|
day: 9
|
||||||
|
stars: 2
|
||||||
|
---
|
||||||
|
|
||||||
|
```python
|
||||||
|
from functools import reduce
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
from numpy.lib.stride_tricks import as_strided
|
||||||
|
|
||||||
|
|
||||||
|
def load() -> np.ndarray:
|
||||||
|
with open("../.input/day09") as f:
|
||||||
|
lines = list(map(str.strip, f.readlines()))
|
||||||
|
return np.array([int(num) for line in lines for num in line]).reshape((len(lines), -1))
|
||||||
|
|
||||||
|
|
||||||
|
def windows(target, shape=(3, 3), stride: int = 1):
|
||||||
|
target = np.pad(target, 1, 'constant', constant_values=9)
|
||||||
|
(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)
|
||||||
|
|
||||||
|
|
||||||
|
def solve1() -> int:
|
||||||
|
hmap = load()
|
||||||
|
return np.sum((hmap + 1) * (hmap == windows(hmap).min(axis=(2, 3))))
|
||||||
|
|
||||||
|
|
||||||
|
def find_basin(areas, visited, x, y) -> int:
|
||||||
|
if visited[x, y]:
|
||||||
|
return 0
|
||||||
|
visited[x, y] = True
|
||||||
|
area = 1
|
||||||
|
for dx, dy in [(x+1, y), (x-1, y), (x, y+1), (x, y-1)]:
|
||||||
|
if (
|
||||||
|
0 <= dx < areas.shape[0] and
|
||||||
|
0 <= dy < areas.shape[1] and
|
||||||
|
areas[dx, dy]
|
||||||
|
):
|
||||||
|
area += find_basin(areas, visited, dx, dy)
|
||||||
|
return area
|
||||||
|
|
||||||
|
|
||||||
|
def solve2() -> int:
|
||||||
|
hmap = load()
|
||||||
|
areas = hmap != 9
|
||||||
|
starts = np.argwhere(hmap == windows(hmap).min(axis=(2, 3)))
|
||||||
|
visited = np.zeros(hmap.shape, dtype=np.bool8)
|
||||||
|
basins = [find_basin(areas, visited, *start) for start in starts]
|
||||||
|
return reduce(lambda acc, num: acc * num, sorted(basins, reverse=True)[:3], 1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print(solve1()) # 522
|
||||||
|
print(solve2()) # 916688
|
||||||
|
```
|
62
src/content/aoc/2021/10.md
Normal file
62
src/content/aoc/2021/10.md
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
---
|
||||||
|
title: "Advent of Code 2021 - Day 10"
|
||||||
|
date: 2021-12-14T23:42:39+01:00
|
||||||
|
year: 2021
|
||||||
|
day: 10
|
||||||
|
stars: 2
|
||||||
|
---
|
||||||
|
|
||||||
|
```python
|
||||||
|
from functools import reduce
|
||||||
|
|
||||||
|
|
||||||
|
def load() -> list[str]:
|
||||||
|
with open('../.input/day10', 'r') as f:
|
||||||
|
return list(map(str.strip, f.readlines()))
|
||||||
|
|
||||||
|
|
||||||
|
opening, closing = ['(', '[', '{', '<'], [')', ']', '}', '>']
|
||||||
|
scores = { ")": 3, "]": 57, "}": 1197, ">": 25137 }
|
||||||
|
|
||||||
|
|
||||||
|
def validate(line: str) -> int:
|
||||||
|
stack = []
|
||||||
|
for char in line:
|
||||||
|
if char in opening:
|
||||||
|
stack.append(char)
|
||||||
|
else:
|
||||||
|
if opening.index(stack.pop()) != closing.index(char):
|
||||||
|
return scores[char]
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def solve1() -> int:
|
||||||
|
return sum(map(validate, load()))
|
||||||
|
|
||||||
|
|
||||||
|
scores2 = { ")": 1, "]": 2, "}": 3, ">": 4 }
|
||||||
|
completion = { o: c for o, c in zip(opening, closing) }
|
||||||
|
|
||||||
|
|
||||||
|
def autocomplete(line: str) -> str:
|
||||||
|
stack = []
|
||||||
|
for char in line:
|
||||||
|
if char in opening:
|
||||||
|
stack.append(char)
|
||||||
|
else:
|
||||||
|
stack.pop()
|
||||||
|
return list(map(lambda x: completion[x], stack))
|
||||||
|
|
||||||
|
|
||||||
|
def solve2() -> int:
|
||||||
|
lines = list(filter(lambda line: validate(line) == 0, load()))
|
||||||
|
return sorted([
|
||||||
|
reduce(lambda acc, char: 5 * acc + scores2[char], reversed(line), 0)
|
||||||
|
for line in map(autocomplete, lines)
|
||||||
|
])[len(lines) // 2]
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print(solve1()) # 345441
|
||||||
|
print(solve2()) # 3235371166
|
||||||
|
```
|
90
src/content/aoc/2021/11.md
Normal file
90
src/content/aoc/2021/11.md
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
---
|
||||||
|
title: "Advent of Code 2021 - Day 11"
|
||||||
|
date: 2021-12-11T09:09:14+01:00
|
||||||
|
year: 2021
|
||||||
|
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
|
||||||
|
```
|
58
src/content/aoc/2021/12.md
Normal file
58
src/content/aoc/2021/12.md
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
---
|
||||||
|
title: "Advent of Code 2021 - Day 12"
|
||||||
|
date: 2021-12-14T23:42:43+01:00
|
||||||
|
year: 2021
|
||||||
|
day: 12
|
||||||
|
stars: 2
|
||||||
|
---
|
||||||
|
|
||||||
|
```python
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
input_pattern = re.compile(r'([a-zA-Z]+)-([a-zA-Z]+)')
|
||||||
|
small_pattern = re.compile(r'([a-z]+)')
|
||||||
|
|
||||||
|
|
||||||
|
def load():
|
||||||
|
with open('../.input/day12', 'r') as f:
|
||||||
|
return input_pattern.findall(f.read())
|
||||||
|
|
||||||
|
|
||||||
|
def construct_paths(graph, start, end, path=None, single_reentry=False):
|
||||||
|
if path is None:
|
||||||
|
path = [start]
|
||||||
|
|
||||||
|
if start == end:
|
||||||
|
yield path
|
||||||
|
else:
|
||||||
|
for node in graph[start]:
|
||||||
|
is_small = small_pattern.match(node)
|
||||||
|
if not is_small or node not in path:
|
||||||
|
yield from construct_paths(graph, node, end, path + [node], single_reentry)
|
||||||
|
elif is_small and single_reentry and node not in ["start", "end"]:
|
||||||
|
yield from construct_paths(graph, node, end, path + [node])
|
||||||
|
|
||||||
|
|
||||||
|
def solve1():
|
||||||
|
graph = {}
|
||||||
|
for node1, node2 in load():
|
||||||
|
graph.setdefault(node1, set()).add(node2)
|
||||||
|
graph.setdefault(node2, set()).add(node1)
|
||||||
|
|
||||||
|
return sum(1 for _ in construct_paths(graph, 'start', 'end'))
|
||||||
|
|
||||||
|
|
||||||
|
def solve2():
|
||||||
|
graph = {}
|
||||||
|
for node1, node2 in load():
|
||||||
|
graph.setdefault(node1, set()).add(node2)
|
||||||
|
graph.setdefault(node2, set()).add(node1)
|
||||||
|
|
||||||
|
return sum(1 for _ in construct_paths(graph, 'start', 'end', single_reentry=True))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print(solve1()) # 4304
|
||||||
|
print(solve2()) # 118242
|
||||||
|
```
|
62
src/content/aoc/2021/13.md
Normal file
62
src/content/aoc/2021/13.md
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
---
|
||||||
|
title: "Advent of Code 2021 - Day 13"
|
||||||
|
date: 2021-12-14T23:42:45+01:00
|
||||||
|
year: 2021
|
||||||
|
day: 13
|
||||||
|
stars: 2
|
||||||
|
---
|
||||||
|
|
||||||
|
```python
|
||||||
|
import re
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
|
pattern_dots = re.compile(r"([0-9]+),([0-9]+)")
|
||||||
|
pattern_folds = re.compile(r"fold along ([xy])=([0-9]+)")
|
||||||
|
|
||||||
|
|
||||||
|
def load():
|
||||||
|
with open("../.input/day13") as f:
|
||||||
|
dots, folds = f.read().split("\n\n")
|
||||||
|
dots = [(int(x), int(y)) for x, y in pattern_dots.findall(dots)]
|
||||||
|
matrix = np.zeros((1 + max(y for _, y in dots), 1 + max(x for x, _ in dots)), dtype=bool)
|
||||||
|
for x, y in dots:
|
||||||
|
matrix[y, x] = True
|
||||||
|
return matrix, [(axis, int(offset)) for axis, offset in pattern_folds.findall(folds)]
|
||||||
|
|
||||||
|
|
||||||
|
def fold(matrix: np.ndarray, axis, offset) -> np.ndarray:
|
||||||
|
if axis == "x":
|
||||||
|
result = matrix[:, :offset]
|
||||||
|
right = np.fliplr(matrix[:, offset+1:])
|
||||||
|
if result.shape == right.shape:
|
||||||
|
result |= right
|
||||||
|
else:
|
||||||
|
result[:, -right.shape[1]:] |= right
|
||||||
|
else:
|
||||||
|
result = matrix[:offset, :]
|
||||||
|
bottom = np.flipud(matrix[offset+1:, :])
|
||||||
|
if result.shape == bottom.shape:
|
||||||
|
result |= bottom
|
||||||
|
else:
|
||||||
|
result[-bottom.shape[0]:, :] |= bottom
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def solve1() -> int:
|
||||||
|
dots, folds = load()
|
||||||
|
return fold(dots, *folds[0]).sum()
|
||||||
|
|
||||||
|
|
||||||
|
def solve2() -> None:
|
||||||
|
dots, folds = load()
|
||||||
|
for axis, offset in folds:
|
||||||
|
dots = fold(dots, axis, offset)
|
||||||
|
[print("".join(line)) for line in np.where(dots, "#", " ")]
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print(solve1()) # 638
|
||||||
|
solve2() # CJCKEBAPB
|
||||||
|
```
|
81
src/content/aoc/2021/14.md
Normal file
81
src/content/aoc/2021/14.md
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
---
|
||||||
|
title: "Advent of Code 2021 - Day 14"
|
||||||
|
date: 2021-12-14T23:42:47+01:00
|
||||||
|
year: 2021
|
||||||
|
day: 14
|
||||||
|
stars: 2
|
||||||
|
---
|
||||||
|
|
||||||
|
```python
|
||||||
|
import re
|
||||||
|
|
||||||
|
input_pattern = re.compile(r"([A-Z]+) -> ([A-Z]+)")
|
||||||
|
|
||||||
|
|
||||||
|
def load():
|
||||||
|
with open("../.input/day14", "r") as f:
|
||||||
|
start, rest = f.read().split("\n\n")
|
||||||
|
return start, input_pattern.findall(rest)
|
||||||
|
|
||||||
|
|
||||||
|
def step(start, rules):
|
||||||
|
output = []
|
||||||
|
for pair in zip(start, start[1:]):
|
||||||
|
c1, c2 = pair
|
||||||
|
output.append(c1)
|
||||||
|
output.append(rules[c1 + c2])
|
||||||
|
output.append(start[-1])
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
def find_counts(start: str, rules: dict[str, str], epochs: int) -> dict[str, int]:
|
||||||
|
for _ in range(epochs):
|
||||||
|
start = step(start, rules)
|
||||||
|
|
||||||
|
chars: dict[str, int] = {}
|
||||||
|
for c in start:
|
||||||
|
chars[c] = chars.get(c, 0) + 1
|
||||||
|
return chars
|
||||||
|
|
||||||
|
|
||||||
|
def solve1() -> int:
|
||||||
|
start, rules = load()
|
||||||
|
rules = {k: v for k, v in rules}
|
||||||
|
chars = find_counts(start, rules, 10)
|
||||||
|
values = list(sorted(chars.values(), reverse=True))
|
||||||
|
return values[0] - values[-1]
|
||||||
|
|
||||||
|
|
||||||
|
def find_counts_optimized(start: str, rules: dict[str, str], epochs: int) -> dict[str, int]:
|
||||||
|
chars: dict[str, int] = {}
|
||||||
|
for c1, c2 in zip(start, start[1:]):
|
||||||
|
chars[c1 + c2] = chars.get(c1 + c2, 0) + 1
|
||||||
|
|
||||||
|
for _ in range(epochs):
|
||||||
|
new_chars: dict[str, int] = {}
|
||||||
|
for combo, count in chars.items():
|
||||||
|
if combo in rules:
|
||||||
|
insert = rules[combo]
|
||||||
|
new_chars[combo[0] + insert] = new_chars.get(combo[0] + insert, 0) + chars[combo]
|
||||||
|
new_chars[insert + combo[1]] = new_chars.get(insert + combo[1], 0) + chars[combo]
|
||||||
|
chars = new_chars
|
||||||
|
|
||||||
|
final_chars: dict[str, int] = {start[-1]: 1}
|
||||||
|
for combo, count in chars.items():
|
||||||
|
final_chars[combo[0]] = final_chars.get(combo[0], 0) + count
|
||||||
|
return final_chars
|
||||||
|
|
||||||
|
|
||||||
|
def solve2():
|
||||||
|
start, rules = load()
|
||||||
|
rules = {k: v for k, v in rules}
|
||||||
|
chars = find_counts_optimized(start, rules, 40)
|
||||||
|
|
||||||
|
values = list(sorted(chars.values(), reverse=True))
|
||||||
|
return values[0] - values[-1]
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print(solve1())
|
||||||
|
print(solve2())
|
||||||
|
```
|
82
src/content/aoc/2021/15.md
Normal file
82
src/content/aoc/2021/15.md
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
---
|
||||||
|
title: "Advent of Code 2021 - Day 15"
|
||||||
|
date: 2021-12-17T00:28:20+01:00
|
||||||
|
year: 2021
|
||||||
|
day: 15
|
||||||
|
stars: 2
|
||||||
|
---
|
||||||
|
|
||||||
|
```python
|
||||||
|
import queue
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
|
def load() -> np.ndarray:
|
||||||
|
with open('../.input/day15') 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)))
|
||||||
|
|
||||||
|
|
||||||
|
def manhattan(node, goal) -> int:
|
||||||
|
return abs(node[0] - goal[0]) + abs(node[1] - goal[1])
|
||||||
|
|
||||||
|
|
||||||
|
def neighbors(node, shape) -> list[tuple[int, int]]:
|
||||||
|
return list(filter(
|
||||||
|
lambda t: 0 <= t[0] < shape[0] and 0 <= t[1] < shape[1],
|
||||||
|
[(node[0] + 1, node[1]), (node[0] - 1, node[1]), (node[0], node[1] + 1), (node[0], node[1] - 1)]
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
def a_star(
|
||||||
|
start: tuple[int, int],
|
||||||
|
goal: tuple[int, int],
|
||||||
|
grid: np.ndarray,
|
||||||
|
) -> list[tuple[int, int]]:
|
||||||
|
frontier: queue.Queue[tuple[int, tuple[int, int]]] = queue.PriorityQueue()
|
||||||
|
parent: dict[tuple[int, int], tuple[int, int]] = {}
|
||||||
|
cost: dict[tuple[int, int], int] = {start: 0}
|
||||||
|
|
||||||
|
frontier.put((0, start))
|
||||||
|
while not frontier.empty():
|
||||||
|
current: tuple[int, int] = frontier.get()[1]
|
||||||
|
if current == goal:
|
||||||
|
break
|
||||||
|
|
||||||
|
for neighbor in neighbors(current, grid.shape):
|
||||||
|
new_cost: int = cost[current] + grid[neighbor[1]][neighbor[0]]
|
||||||
|
if neighbor not in cost or new_cost < cost[neighbor]:
|
||||||
|
cost[neighbor], parent[neighbor] = new_cost, current
|
||||||
|
frontier.put((
|
||||||
|
new_cost + manhattan(neighbor, goal),
|
||||||
|
neighbor
|
||||||
|
))
|
||||||
|
|
||||||
|
path: list[tuple[int, int]] = [goal]
|
||||||
|
while path[-1] != start:
|
||||||
|
path.append(parent[path[-1]])
|
||||||
|
return path[::-1]
|
||||||
|
|
||||||
|
|
||||||
|
def solve1() -> int:
|
||||||
|
grid = load()
|
||||||
|
path = a_star((0, 0), (len(grid[0]) - 1, len(grid) - 1), grid)
|
||||||
|
return sum(grid[y][x] for x, y in path[1:])
|
||||||
|
|
||||||
|
|
||||||
|
def solve2() -> int:
|
||||||
|
full_grid = np.tile((grid := load()), (5, 5))
|
||||||
|
s_x, s_y = grid.shape[0], grid.shape[1]
|
||||||
|
for i in range(5):
|
||||||
|
for j in range(5):
|
||||||
|
full_grid[i*s_y:i*s_y+s_y, j*s_x:j*s_x+s_x] += np.full_like(grid, i+j)
|
||||||
|
full_grid = full_grid % 10 + (full_grid >= 10).astype(int)
|
||||||
|
path = a_star((0, 0), (full_grid.shape[0] - 1, full_grid.shape[1] - 1), full_grid)
|
||||||
|
return sum(full_grid[y][x] for x, y in path[1:])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print(solve1()) # 748
|
||||||
|
print(solve2()) # 3045
|
||||||
|
```
|
31
src/content/aoc/2021/16.md
Normal file
31
src/content/aoc/2021/16.md
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
---
|
||||||
|
title: "Advent of Code 2021 - Day 16"
|
||||||
|
date: 2021-12-18T01:44:09+01:00
|
||||||
|
year: 2021
|
||||||
|
day: 16
|
||||||
|
stars: 0
|
||||||
|
---
|
||||||
|
|
||||||
|
Today's challenge is [here](https://adventofcode.com/2021/day/16).
|
||||||
|
|
||||||
|
My first attempt at solving this puzzle was to use a library called [construct](https://pypi.org/project/construct/) to parse the data, however it turned out to be too hard for me xD
|
||||||
|
|
||||||
|
I managed to create a parser for the literals:
|
||||||
|
```python
|
||||||
|
literal = Bitwise(Aligned(8, Struct(
|
||||||
|
"version" / BitsInteger(3),
|
||||||
|
"type" / Const(0b100, BitsInteger(3)),
|
||||||
|
"sequence" / RepeatUntil(
|
||||||
|
lambda x, _, __: x.flag == 0,
|
||||||
|
Struct(
|
||||||
|
"flag" / Flag,
|
||||||
|
"data" / Nibble,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
"value" / Computed(lambda ctx: reduce(lambda acc, n: (acc << 4) + n.data, ctx.sequence, 0)),
|
||||||
|
)))
|
||||||
|
|
||||||
|
assert(literal.parse(b"\xD2\xFE\x28").value == 2021)
|
||||||
|
```
|
||||||
|
|
||||||
|
This does work as excepted, but given that the entire problem is a tree of structures, I might just write a bunch of functions to parse the data instead. In construct the recursion has to be done by using `LazyBound` as far as I can tell, which doesn't really work with `Bitwise`.
|
15
src/content/config.ts
Normal file
15
src/content/config.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import { defineCollection, z } from 'astro:content';
|
||||||
|
|
||||||
|
|
||||||
|
export const collections = {
|
||||||
|
aoc: defineCollection({
|
||||||
|
schema: z.object({
|
||||||
|
title: z.string(),
|
||||||
|
date: z.date(),
|
||||||
|
year: z.number(),
|
||||||
|
day: z.number(),
|
||||||
|
stars: z.number(),
|
||||||
|
math: z.boolean().optional()
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
}
|
1
src/env.d.ts
vendored
1
src/env.d.ts
vendored
|
@ -1 +1,2 @@
|
||||||
|
/// <reference path="../.astro/types.d.ts" />
|
||||||
/// <reference types="astro/client" />
|
/// <reference types="astro/client" />
|
||||||
|
|
|
@ -5,7 +5,7 @@ import Header from "../components/headers/Article.astro";
|
||||||
import Toc from "../components/Toc.astro";
|
import Toc from "../components/Toc.astro";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
headings: any[],
|
headings?: any[],
|
||||||
frontmatter: {
|
frontmatter: {
|
||||||
title: string;
|
title: string;
|
||||||
date: string;
|
date: string;
|
||||||
|
@ -25,7 +25,7 @@ const {
|
||||||
<main class="l-article">
|
<main class="l-article">
|
||||||
<aside class="l-article__aside">
|
<aside class="l-article__aside">
|
||||||
<div class="l-article__toc">
|
<div class="l-article__toc">
|
||||||
<Toc headings={headings} />
|
{headings && (<Toc headings={headings} />)}
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
<article class="l-article__article marker readable">
|
<article class="l-article__article marker readable">
|
||||||
|
|
16
src/pages/aoc/[...slug].astro
Normal file
16
src/pages/aoc/[...slug].astro
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
---
|
||||||
|
import Article from '../../layouts/Article.astro';
|
||||||
|
import { getCollection } from 'astro:content';
|
||||||
|
|
||||||
|
|
||||||
|
export async function getStaticPaths () {
|
||||||
|
return (await getCollection('aoc'))
|
||||||
|
.map(entry => ({params: {slug: entry.slug}, props: {entry}}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const { entry } = Astro.props;
|
||||||
|
const { Content } = await entry.render();
|
||||||
|
---
|
||||||
|
<Article frontmatter={entry.data}>
|
||||||
|
<Content />
|
||||||
|
</Article>
|
18
src/pages/aoc/[year].astro
Normal file
18
src/pages/aoc/[year].astro
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
---
|
||||||
|
import { getCollection } from 'astro:content';
|
||||||
|
|
||||||
|
export async function getStaticPaths () {
|
||||||
|
return (await getCollection('aoc'))
|
||||||
|
.map(entry => entry.slug.slice(0, 4))
|
||||||
|
.map(year => ({params: {year}, props: {year}}))
|
||||||
|
}
|
||||||
|
|
||||||
|
const { year } = Astro.props;
|
||||||
|
const pages = await getCollection('aoc', (entry) => entry.id.startsWith(Astro.params.year!));
|
||||||
|
---
|
||||||
|
|
||||||
|
{pages.map(entry => (
|
||||||
|
<div>
|
||||||
|
{entry.id}
|
||||||
|
</div>
|
||||||
|
))}
|
10
src/pages/aoc/index.astro
Normal file
10
src/pages/aoc/index.astro
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
import { getCollection } from 'astro:content';
|
||||||
|
|
||||||
|
|
||||||
|
const posts = await getCollection('aoc');
|
||||||
|
---
|
||||||
|
|
||||||
|
<div>{posts.length}</div>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue