Implement day 10 solver
This commit is contained in:
138
puzzles/10.py
Normal file
138
puzzles/10.py
Normal file
@@ -0,0 +1,138 @@
|
||||
from dataclasses import dataclass
|
||||
from textwrap import dedent
|
||||
from typing import NamedTuple, override
|
||||
from unittest import TestCase
|
||||
|
||||
from puzzles._solver import Solver
|
||||
|
||||
|
||||
class TrailSet(NamedTuple):
|
||||
trailhead: tuple[int, int]
|
||||
trailends: set[tuple[int, int]]
|
||||
count: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class TopographicalMap:
|
||||
grid: list[list[str]]
|
||||
trailheads: set[tuple[int, int]]
|
||||
|
||||
@classmethod
|
||||
def parse(cls, raw_map: str) -> "TopographicalMap":
|
||||
grid = []
|
||||
trailheads = set()
|
||||
|
||||
for x, row in enumerate(raw_map.strip().splitlines()):
|
||||
grid_row = []
|
||||
|
||||
for y, cell in enumerate(row):
|
||||
height = int(cell)
|
||||
grid_row.append(height)
|
||||
if height == 0:
|
||||
trailheads.add((x, y))
|
||||
|
||||
grid.append(grid_row)
|
||||
|
||||
return cls(grid, trailheads)
|
||||
|
||||
def trails(self) -> list[TrailSet]:
|
||||
trails = []
|
||||
|
||||
for trailhead in self.trailheads:
|
||||
trailends: set[tuple, tuple] = set()
|
||||
count = 0
|
||||
x, y = trailhead
|
||||
height = self.grid[x][y]
|
||||
|
||||
def follow_trail(x, y, height):
|
||||
nonlocal count
|
||||
|
||||
if height == 9:
|
||||
trailends.add((x, y))
|
||||
count += 1
|
||||
return
|
||||
|
||||
if x > 0 and self.grid[x - 1][y] == height + 1:
|
||||
follow_trail(x - 1, y, height + 1)
|
||||
if x < len(self.grid) - 1 and self.grid[x + 1][y] == height + 1:
|
||||
follow_trail(x + 1, y, height + 1)
|
||||
if y > 0 and self.grid[x][y - 1] == height + 1:
|
||||
follow_trail(x, y - 1, height + 1)
|
||||
if y < len(self.grid[0]) - 1 and self.grid[x][y + 1] == height + 1:
|
||||
follow_trail(x, y + 1, height + 1)
|
||||
|
||||
follow_trail(x, y, height)
|
||||
|
||||
trails.append(TrailSet(trailhead, trailends, count))
|
||||
|
||||
return trails
|
||||
|
||||
|
||||
class DayTenSolver(Solver):
|
||||
topographical_map: TopographicalMap
|
||||
|
||||
@override
|
||||
def __init__(self, puzzle_input: str):
|
||||
self.topographical_map = TopographicalMap.parse(puzzle_input)
|
||||
|
||||
@override
|
||||
def solve_p1(self) -> int:
|
||||
trail_sets = self.topographical_map.trails()
|
||||
return sum(len(trail_set.trailends) for trail_set in trail_sets)
|
||||
|
||||
@override
|
||||
def solve_p2(self) -> int:
|
||||
trail_sets = self.topographical_map.trails()
|
||||
return sum(trail_set.count for trail_set in trail_sets)
|
||||
|
||||
|
||||
class TestDayTenSolver(TestCase):
|
||||
test_input = dedent(
|
||||
"""
|
||||
89010123
|
||||
78121874
|
||||
87430965
|
||||
96549874
|
||||
45678903
|
||||
32019012
|
||||
01329801
|
||||
10456732
|
||||
"""
|
||||
)
|
||||
|
||||
def test_parse(self):
|
||||
solver = DayTenSolver(self.test_input)
|
||||
self.assertEqual(
|
||||
solver.topographical_map,
|
||||
TopographicalMap(
|
||||
grid=[
|
||||
[8, 9, 0, 1, 0, 1, 2, 3],
|
||||
[7, 8, 1, 2, 1, 8, 7, 4],
|
||||
[8, 7, 4, 3, 0, 9, 6, 5],
|
||||
[9, 6, 5, 4, 9, 8, 7, 4],
|
||||
[4, 5, 6, 7, 8, 9, 0, 3],
|
||||
[3, 2, 0, 1, 9, 0, 1, 2],
|
||||
[0, 1, 3, 2, 9, 8, 0, 1],
|
||||
[1, 0, 4, 5, 6, 7, 3, 2],
|
||||
],
|
||||
trailheads={
|
||||
(0, 2),
|
||||
(0, 4),
|
||||
(2, 4),
|
||||
(4, 6),
|
||||
(5, 2),
|
||||
(5, 5),
|
||||
(6, 0),
|
||||
(6, 6),
|
||||
(7, 1),
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
def test_solve_p1(self):
|
||||
solver = DayTenSolver(self.test_input)
|
||||
self.assertEqual(solver.solve_p1(), 36)
|
||||
|
||||
def test_solve_p2(self):
|
||||
solver = DayTenSolver(self.test_input)
|
||||
self.assertEqual(solver.solve_p2(), 81)
|
Reference in New Issue
Block a user