Restructure solver modules with classes and unittest suites
This commit is contained in:
132
puzzles/2.py
132
puzzles/2.py
@@ -1,63 +1,93 @@
|
||||
from itertools import islice
|
||||
from typing import Iterator
|
||||
from dataclasses import dataclass
|
||||
from functools import cached_property
|
||||
from itertools import pairwise
|
||||
from textwrap import dedent
|
||||
from typing import Iterator, override
|
||||
from unittest import TestCase
|
||||
|
||||
from more_itertools import ilen
|
||||
|
||||
test_input = """
|
||||
7 6 4 2 1
|
||||
1 2 7 8 9
|
||||
9 7 6 2 1
|
||||
1 3 2 4 5
|
||||
8 6 4 4 1
|
||||
1 3 6 7 9
|
||||
"""
|
||||
|
||||
test_solution_p1 = 2
|
||||
test_solution_p2 = 4
|
||||
from puzzles._solver import Solver
|
||||
|
||||
|
||||
def solve_p1(puzzle_input: str) -> int:
|
||||
reports = _parse_reports(puzzle_input)
|
||||
delta_reports = (_deltas(report) for report in reports)
|
||||
safe_delta_reports = filter(_is_gradual_monotonic, delta_reports)
|
||||
return ilen(safe_delta_reports)
|
||||
@dataclass
|
||||
class Report:
|
||||
levels: tuple[int, ...]
|
||||
|
||||
@cached_property
|
||||
def deltas(self) -> tuple[int, ...]:
|
||||
return tuple(b - a for a, b in pairwise(self.levels))
|
||||
|
||||
def is_gradual_monotonic(self) -> bool:
|
||||
sign = 1 if self.deltas[0] > 0 else -1
|
||||
return all(
|
||||
delta != 0 and delta / abs(delta) == sign and abs(delta) < 4
|
||||
for delta in self.deltas
|
||||
)
|
||||
|
||||
def permute_dampened_variations(self) -> Iterator["Report"]:
|
||||
yield self
|
||||
for i in range(0, len(self.levels)):
|
||||
yield Report(self.levels[:i] + self.levels[i + 1 :])
|
||||
|
||||
|
||||
def solve_p2(puzzle_input: str) -> int:
|
||||
reports = _parse_reports(puzzle_input)
|
||||
dampened_report_collections = (_dampen_permutations(report) for report in reports)
|
||||
delta_report_collections = (
|
||||
(_deltas(report) for report in report_collection)
|
||||
for report_collection in dampened_report_collections
|
||||
)
|
||||
safe_report_collections = filter(
|
||||
lambda delta_report_collection: any(
|
||||
_is_gradual_monotonic(delta_report)
|
||||
for delta_report in delta_report_collection
|
||||
),
|
||||
delta_report_collections,
|
||||
)
|
||||
return ilen(safe_report_collections)
|
||||
class DayTwoSolver(Solver):
|
||||
reports: list[Report]
|
||||
|
||||
@override
|
||||
def __init__(self, puzzle_input: str):
|
||||
self.reports = list(
|
||||
Report(tuple(int(level) for level in line.split(" ")))
|
||||
for line in puzzle_input.strip().splitlines()
|
||||
)
|
||||
|
||||
@override
|
||||
def solve_p1(self) -> int:
|
||||
safe_reports = filter(Report.is_gradual_monotonic, self.reports)
|
||||
return ilen(safe_reports)
|
||||
|
||||
@override
|
||||
def solve_p2(self) -> int:
|
||||
safe_reports = filter(
|
||||
lambda report: any(
|
||||
report_variation.is_gradual_monotonic()
|
||||
for report_variation in report.permute_dampened_variations()
|
||||
),
|
||||
self.reports,
|
||||
)
|
||||
return ilen(safe_reports)
|
||||
|
||||
|
||||
def _parse_reports(puzzle_input: str) -> Iterator[tuple[int, ...]]:
|
||||
lines = puzzle_input.strip().splitlines()
|
||||
return (tuple(int(level) for level in line.split(" ")) for line in lines)
|
||||
|
||||
|
||||
def _deltas(report: tuple[int, ...]) -> tuple[int, ...]:
|
||||
pairs = zip(report, islice(report, 1, None))
|
||||
return tuple(b - a for a, b in pairs)
|
||||
|
||||
|
||||
def _is_gradual_monotonic(delta_report: tuple[int]) -> bool:
|
||||
sign = 1 if delta_report[0] > 0 else -1
|
||||
return all(
|
||||
delta != 0 and delta / abs(delta) == sign and abs(delta) < 4
|
||||
for delta in delta_report
|
||||
class TestDayNSolver(TestCase):
|
||||
test_input = dedent(
|
||||
"""
|
||||
7 6 4 2 1
|
||||
1 2 7 8 9
|
||||
9 7 6 2 1
|
||||
1 3 2 4 5
|
||||
8 6 4 4 1
|
||||
1 3 6 7 9
|
||||
"""
|
||||
)
|
||||
|
||||
def test_parse(self):
|
||||
solver = DayTwoSolver(self.test_input)
|
||||
self.assertListEqual(
|
||||
solver.reports,
|
||||
[
|
||||
Report((7, 6, 4, 2, 1)),
|
||||
Report((1, 2, 7, 8, 9)),
|
||||
Report((9, 7, 6, 2, 1)),
|
||||
Report((1, 3, 2, 4, 5)),
|
||||
Report((8, 6, 4, 4, 1)),
|
||||
Report((1, 3, 6, 7, 9)),
|
||||
],
|
||||
)
|
||||
|
||||
def _dampen_permutations(report: tuple[int, ...]) -> Iterator[tuple[int, ...]]:
|
||||
yield report
|
||||
yield from (report[:i] + report[i + 1 :] for i in range(0, len(report)))
|
||||
def test_solve_p1(self):
|
||||
solver = DayTwoSolver(self.test_input)
|
||||
self.assertEqual(solver.solve_p1(), 2)
|
||||
|
||||
def test_solve_p2(self):
|
||||
solver = DayTwoSolver(self.test_input)
|
||||
self.assertEqual(solver.solve_p2(), 4)
|
||||
|
Reference in New Issue
Block a user