mirror of
https://github.com/bertptrs/adventofcode.git
synced 2025-12-25 12:50:32 +01:00
Implement 2024 day 15
This commit is contained in:
167
2024/src/aoc/days/day15.py
Normal file
167
2024/src/aoc/days/day15.py
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
import itertools
|
||||||
|
|
||||||
|
import numpy
|
||||||
|
|
||||||
|
from . import SeparateRunner
|
||||||
|
|
||||||
|
|
||||||
|
def parse_input(data: str) -> tuple[numpy.array, str]:
|
||||||
|
grid, steps = data.split("\n\n")
|
||||||
|
|
||||||
|
grid_split = numpy.array([list(line) for line in grid.split("\n")])
|
||||||
|
|
||||||
|
steps = "".join(steps.split("\n"))
|
||||||
|
|
||||||
|
return grid_split, steps
|
||||||
|
|
||||||
|
|
||||||
|
class DayRunner(SeparateRunner):
|
||||||
|
@classmethod
|
||||||
|
def part1(cls, input: str) -> None:
|
||||||
|
grid, steps = parse_input(input)
|
||||||
|
|
||||||
|
y, x = numpy.where(grid == "@")
|
||||||
|
x, y = x[0], y[0]
|
||||||
|
|
||||||
|
for c in steps:
|
||||||
|
match c:
|
||||||
|
case "^":
|
||||||
|
dx, dy = 0, -1
|
||||||
|
case ">":
|
||||||
|
dx, dy = 1, 0
|
||||||
|
case "<":
|
||||||
|
dx, dy = -1, 0
|
||||||
|
case "v":
|
||||||
|
dx, dy = 0, 1
|
||||||
|
case other:
|
||||||
|
raise ValueError(f"Invalid movement: {other}")
|
||||||
|
|
||||||
|
match grid[y + dy, x + dx]:
|
||||||
|
case "#":
|
||||||
|
continue
|
||||||
|
case "O":
|
||||||
|
crashed = False
|
||||||
|
for dist in itertools.count(2):
|
||||||
|
match grid[y + dist * dy, x + dist * dx]:
|
||||||
|
case "O":
|
||||||
|
continue
|
||||||
|
case "#":
|
||||||
|
crashed = True
|
||||||
|
break
|
||||||
|
case _:
|
||||||
|
crashed = False
|
||||||
|
break
|
||||||
|
|
||||||
|
if crashed:
|
||||||
|
continue
|
||||||
|
|
||||||
|
grid[y + dist * dy, x + dist * dx] = "O"
|
||||||
|
case _:
|
||||||
|
pass
|
||||||
|
|
||||||
|
grid[y, x] = "."
|
||||||
|
x += dx
|
||||||
|
y += dy
|
||||||
|
grid[y, x] = "@"
|
||||||
|
|
||||||
|
stones = numpy.where(grid == "O")
|
||||||
|
|
||||||
|
return sum(100 * y + x for y, x in zip(*stones))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def part2(cls, input: str) -> None:
|
||||||
|
input = input.replace(".", "..")
|
||||||
|
input = input.replace("#", "##")
|
||||||
|
input = input.replace("O", "[]")
|
||||||
|
input = input.replace("@", "@.")
|
||||||
|
|
||||||
|
grid, steps = parse_input(input)
|
||||||
|
|
||||||
|
y, x = numpy.where(grid == "@")
|
||||||
|
x, y = x[0], y[0]
|
||||||
|
|
||||||
|
for c in steps:
|
||||||
|
match c:
|
||||||
|
case "^":
|
||||||
|
dx, dy = 0, -1
|
||||||
|
case ">":
|
||||||
|
dx, dy = 1, 0
|
||||||
|
case "<":
|
||||||
|
dx, dy = -1, 0
|
||||||
|
case "v":
|
||||||
|
dx, dy = 0, 1
|
||||||
|
case other:
|
||||||
|
raise ValueError(f"Invalid movement: {other}")
|
||||||
|
|
||||||
|
match grid[y + dy, x + dx]:
|
||||||
|
case "#":
|
||||||
|
continue
|
||||||
|
case "]" | "[":
|
||||||
|
crashed = False
|
||||||
|
if dy == 0:
|
||||||
|
# easy case: just move linearly
|
||||||
|
for dist in itertools.count(2):
|
||||||
|
match grid[y, x + dist * dx]:
|
||||||
|
case "[" | "]":
|
||||||
|
continue
|
||||||
|
case "#":
|
||||||
|
crashed = True
|
||||||
|
break
|
||||||
|
case _:
|
||||||
|
break
|
||||||
|
|
||||||
|
if crashed:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# shuffle all grid points one over
|
||||||
|
for steps in range(dist, 1, -1):
|
||||||
|
grid[y, x + dx * steps] = grid[y, x + dx * (steps - 1)]
|
||||||
|
else:
|
||||||
|
if grid[y + dy, x] == "[":
|
||||||
|
to_check = {x, x + 1}
|
||||||
|
else:
|
||||||
|
to_check = {x, x - 1}
|
||||||
|
|
||||||
|
moving_stones = [to_check]
|
||||||
|
|
||||||
|
for dist in itertools.count(2):
|
||||||
|
to_check_next = set()
|
||||||
|
|
||||||
|
for cx in to_check:
|
||||||
|
match grid[y + dist * dy, cx]:
|
||||||
|
case "#":
|
||||||
|
crashed = True
|
||||||
|
break
|
||||||
|
case "[":
|
||||||
|
to_check_next.add(cx)
|
||||||
|
to_check_next.add(cx + 1)
|
||||||
|
case "]":
|
||||||
|
to_check_next.add(cx)
|
||||||
|
to_check_next.add(cx - 1)
|
||||||
|
case _:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if crashed or not to_check_next:
|
||||||
|
break
|
||||||
|
moving_stones.append(to_check_next)
|
||||||
|
to_check = to_check_next
|
||||||
|
|
||||||
|
if crashed:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for steps in range(len(moving_stones), 0, -1):
|
||||||
|
dist = steps + 1
|
||||||
|
for cx in moving_stones[steps - 1]:
|
||||||
|
grid[y + dy * dist, cx] = grid[y + dy * (dist - 1), cx]
|
||||||
|
grid[y + dy * (dist - 1), cx] = "."
|
||||||
|
case _:
|
||||||
|
pass
|
||||||
|
|
||||||
|
grid[y, x] = "."
|
||||||
|
x += dx
|
||||||
|
y += dy
|
||||||
|
grid[y, x] = "@"
|
||||||
|
|
||||||
|
stones = numpy.where(grid == "[")
|
||||||
|
|
||||||
|
return sum(100 * y + x for y, x in zip(*stones))
|
||||||
21
2024/tests/samples/15.1.txt
Normal file
21
2024/tests/samples/15.1.txt
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
##########
|
||||||
|
#..O..O.O#
|
||||||
|
#......O.#
|
||||||
|
#.OO..O.O#
|
||||||
|
#..O@..O.#
|
||||||
|
#O#..O...#
|
||||||
|
#O..O..O.#
|
||||||
|
#.OO.O.OO#
|
||||||
|
#....O...#
|
||||||
|
##########
|
||||||
|
|
||||||
|
<vv>^<v^>v>^vv^v>v<>v^v<v<^vv<<<^><<><>>v<vvv<>^v^>^<<<><<v<<<v^vv^v>^
|
||||||
|
vvv<<^>^v^^><<>>><>^<<><^vv^^<>vvv<>><^^v>^>vv<>v<<<<v<^v>^<^^>>>^<v<v
|
||||||
|
><>vv>v^v^<>><>>>><^^>vv>v<^^^>>v^v^<^^>v^^>v^<^v>v<>>v^v^<v>v^^<^^vv<
|
||||||
|
<<v<^>>^^^^>>>v^<>vvv^><v<<<>^^^vv^<vvv>^>v<^^^^v<>^>vvvv><>>v^<<^^^^^
|
||||||
|
^><^><>>><>^^<<^^v>>><^<v>^<vv>>v>>>^v><>^v><<<<v>>v<v<v>vvv>^<><<>^><
|
||||||
|
^>><>^v<><^vvv<^^<><v<<<<<><^v<<<><<<^^<v<^^^><^>>^<v^><<<^>>^v<v^v<v^
|
||||||
|
>^>>^v>vv>^<<^v<>><<><<v<<v><>v<^vv<<<>^^v^>^^>>><<^v>>v^v><^^>>^<>vv^
|
||||||
|
<><^^>^^^<><vvvvv^v<v<<>^v<v>v<<^><<><<><<<^^<<<^<<>><<><^^^>^^<>^>v<>
|
||||||
|
^^>vv<^v^v<vv>^<><v<^v>^^^>>>^^vvv^>vvv<>>>^<^>>>>>^<<^v>^vvv<>^<><<v>
|
||||||
|
v^^>>><<^^<>>^v^<v^vv<>v^<<>^<^v^v><^<<<><<^<v><v<>vv>>v><v^<vv<>v^<<^
|
||||||
10
2024/tests/samples/15.2.txt
Normal file
10
2024/tests/samples/15.2.txt
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
########
|
||||||
|
#..O.O.#
|
||||||
|
##@.O..#
|
||||||
|
#...O..#
|
||||||
|
#.#.O..#
|
||||||
|
#...O..#
|
||||||
|
#......#
|
||||||
|
########
|
||||||
|
|
||||||
|
<^^>>>vv<v>>v<<
|
||||||
9
2024/tests/samples/15.3.txt
Normal file
9
2024/tests/samples/15.3.txt
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#######
|
||||||
|
#...#.#
|
||||||
|
#.....#
|
||||||
|
#..OO@#
|
||||||
|
#..O..#
|
||||||
|
#.....#
|
||||||
|
#######
|
||||||
|
|
||||||
|
<vv<<^^<<^^
|
||||||
27
2024/tests/test_day15.py
Normal file
27
2024/tests/test_day15.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from aoc.days.day15 import DayRunner
|
||||||
|
|
||||||
|
from . import get_data
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"data,result",
|
||||||
|
[
|
||||||
|
(get_data(15, 1), 10092),
|
||||||
|
(get_data(15, 2), 2028),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_sample_part1(data: str, result: int) -> None:
|
||||||
|
assert DayRunner.part1(data) == result
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"data,result",
|
||||||
|
[
|
||||||
|
(get_data(15, 1), 9021),
|
||||||
|
(get_data(15, 3), 618),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_sample_part2(data: str, result: int) -> None:
|
||||||
|
assert DayRunner.part2(data) == result
|
||||||
Reference in New Issue
Block a user