68 lines
1.6 KiB
Python
68 lines
1.6 KiB
Python
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}")
|