from itertools import islice from typing import Iterator 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 """.strip() test_solution_p1 = 2 test_solution_p2 = 4 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) 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) def _parse_reports(puzzle_input: str) -> Iterator[tuple[int, ...]]: lines = puzzle_input.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 ) 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)))