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 from puzzles._solver import Solver @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"]: return ( Report(self.levels[:i] + self.levels[i + 1 :]) for i in range(0, len(self.levels) + 1) ) 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) class TestDayNSolver(TestCase): def test(self): solver = DayTwoSolver( 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 """ ) ) 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)), ], ) self.assertEqual(solver.solve_p1(), 2) self.assertEqual(solver.solve_p2(), 4)