Implement day 14 solver
This commit is contained in:
@@ -2,6 +2,7 @@ import re
|
|||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
from typing import Iterator, NamedTuple, override
|
from typing import Iterator, NamedTuple, override
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
|
|
||||||
from puzzles._solver import Solver
|
from puzzles._solver import Solver
|
||||||
|
|
||||||
|
|
||||||
|
133
puzzles/14.py
Normal file
133
puzzles/14.py
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
import re
|
||||||
|
from collections import Counter
|
||||||
|
from itertools import count
|
||||||
|
from math import prod
|
||||||
|
from textwrap import dedent
|
||||||
|
from typing import NamedTuple, override
|
||||||
|
from unittest import TestCase
|
||||||
|
|
||||||
|
from more_itertools import filter_map, first_true, iterate
|
||||||
|
|
||||||
|
from puzzles._solver import Solver
|
||||||
|
|
||||||
|
|
||||||
|
class Robot(NamedTuple):
|
||||||
|
x: int
|
||||||
|
y: int
|
||||||
|
dx: int
|
||||||
|
dy: int
|
||||||
|
|
||||||
|
|
||||||
|
robot_spec_pattern = re.compile(r"p=(-?\d+),(-?\d+) v=(-?\d+),(-?\d+)")
|
||||||
|
|
||||||
|
|
||||||
|
class DayFourteenSolver(Solver):
|
||||||
|
robots: list[Robot]
|
||||||
|
width: int
|
||||||
|
height: int
|
||||||
|
|
||||||
|
@override
|
||||||
|
def __init__(self, puzzle_input: str, width=101, height=103):
|
||||||
|
self.robots = [
|
||||||
|
Robot(*map(int, match.groups()))
|
||||||
|
for match in robot_spec_pattern.finditer(puzzle_input)
|
||||||
|
]
|
||||||
|
self.width = width
|
||||||
|
self.height = height
|
||||||
|
|
||||||
|
@override
|
||||||
|
def solve_p1(self) -> int:
|
||||||
|
quadrant_counts = Counter(
|
||||||
|
filter_map(
|
||||||
|
lambda r: self.get_quadrant(self.move_robot(r, 100)),
|
||||||
|
self.robots,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return prod(quadrant_counts.values())
|
||||||
|
|
||||||
|
@override
|
||||||
|
def solve_p2(self) -> int:
|
||||||
|
state = iterate(
|
||||||
|
lambda robots: [self.move_robot(robot, 1) for robot in robots],
|
||||||
|
self.robots,
|
||||||
|
)
|
||||||
|
return first_true(
|
||||||
|
count(),
|
||||||
|
pred=lambda i: self.total_overlap(next(state)) == 0,
|
||||||
|
)
|
||||||
|
|
||||||
|
def move_robot(self, robot: Robot, seconds: int) -> Robot:
|
||||||
|
return Robot(
|
||||||
|
(robot.x + robot.dx * seconds) % self.width,
|
||||||
|
(robot.y + robot.dy * seconds) % self.height,
|
||||||
|
robot.dx,
|
||||||
|
robot.dy,
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_quadrant(self, robot: Robot) -> int | None:
|
||||||
|
if robot.x > self.width // 2 and robot.y < self.height // 2:
|
||||||
|
return 1
|
||||||
|
if robot.x < self.width // 2 and robot.y < self.height // 2:
|
||||||
|
return 2
|
||||||
|
if robot.x < self.width // 2 and robot.y > self.height // 2:
|
||||||
|
return 3
|
||||||
|
if robot.x > self.width // 2 and robot.y > self.height // 2:
|
||||||
|
return 4
|
||||||
|
return None
|
||||||
|
|
||||||
|
def total_overlap(self, robots: list[Robot]) -> int:
|
||||||
|
locations = Counter((robot.x, robot.y) for robot in robots)
|
||||||
|
return sum(count for count in locations.values() if count > 1)
|
||||||
|
|
||||||
|
|
||||||
|
class TestDayFourteenSolver(TestCase):
|
||||||
|
def test(self):
|
||||||
|
solver = DayFourteenSolver(
|
||||||
|
dedent(
|
||||||
|
"""
|
||||||
|
p=0,4 v=3,-3
|
||||||
|
p=6,3 v=-1,-3
|
||||||
|
p=10,3 v=-1,2
|
||||||
|
p=2,0 v=2,-1
|
||||||
|
p=0,0 v=1,3
|
||||||
|
p=3,0 v=-2,-2
|
||||||
|
p=7,6 v=-1,-3
|
||||||
|
p=3,0 v=-1,-2
|
||||||
|
p=9,3 v=2,3
|
||||||
|
p=7,3 v=-1,2
|
||||||
|
p=2,4 v=2,-3
|
||||||
|
p=9,5 v=-3,-3
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
width=11,
|
||||||
|
height=7,
|
||||||
|
)
|
||||||
|
self.assertTupleEqual(
|
||||||
|
solver.robots[0],
|
||||||
|
Robot(0, 4, 3, -3),
|
||||||
|
)
|
||||||
|
self.assertTupleEqual(
|
||||||
|
solver.move_robot(solver.robots[0], 1),
|
||||||
|
Robot(3, 1, 3, -3),
|
||||||
|
)
|
||||||
|
self.assertEqual(solver.get_quadrant(Robot(0, 0, 0, 0)), 2)
|
||||||
|
self.assertEqual(solver.get_quadrant(Robot(5, 0, 0, 0)), None)
|
||||||
|
self.assertEqual(
|
||||||
|
solver.total_overlap(
|
||||||
|
[
|
||||||
|
Robot(0, 0, 0, 0),
|
||||||
|
Robot(0, 0, 0, 0),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
2,
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
solver.total_overlap(
|
||||||
|
[
|
||||||
|
Robot(0, 0, 0, 0),
|
||||||
|
Robot(1, 0, 0, 0),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
self.assertEqual(solver.solve_p1(), 12)
|
@@ -1,5 +1,5 @@
|
|||||||
from itertools import count, pairwise
|
|
||||||
import math
|
import math
|
||||||
|
from itertools import count, pairwise
|
||||||
from typing import Iterable, NamedTuple, override
|
from typing import Iterable, NamedTuple, override
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user