import operator from collections.abc import Callable from dataclasses import dataclass from pathlib import Path @dataclass(frozen=True, slots=True) class Range: lower: int upper: int @classmethod def from_pair(cls, s: str) -> "Range": left, right = [int(num) for num in s.split("-")] return cls(left, right) @staticmethod def _compare(a: "Range", b: "Range", _op: Callable[[bool, bool], bool]): if _op( b.lower <= a.lower <= b.upper, b.lower <= a.upper <= b.upper, ): return True if _op( a.lower <= b.lower <= a.upper, a.lower <= b.upper <= a.upper, ): return True return False def fully_contains(self, other: "Range") -> bool: return Range._compare(self, other, operator.and_) def overlaps(self, other: "Range") -> bool: return Range._compare(self, other, operator.or_) def part1(data: str) -> int: count = 0 for line in data.splitlines(): left, right = [Range.from_pair(s) for s in line.split(",")] if left.fully_contains(right): count += 1 return count def part2(data: str) -> int: count = 0 for line in data.splitlines(): left, right = [Range.from_pair(s) for s in line.split(",")] if left.overlaps(right): count += 1 return count # Setup data = (Path(__file__).parents[1] / "input").read_text() # Problem solving - part 1 result = part1(data) print(f"Part 1 solution: {result}") # Problem solving - part 2 result = part2(data) print(f"Part 2 solution: {result}")