Python: Day 4 solution
This commit is contained in:
67
04/py/solution.py
Normal file
67
04/py/solution.py
Normal file
@@ -0,0 +1,67 @@
|
||||
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}")
|
||||
55
04/py/test_day04.py
Normal file
55
04/py/test_day04.py
Normal file
@@ -0,0 +1,55 @@
|
||||
import pytest
|
||||
|
||||
from solution import Range, part1, part2
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def example() -> str:
|
||||
return """2-4,6-8
|
||||
2-3,4-5
|
||||
5-7,7-9
|
||||
2-8,3-7
|
||||
6-6,4-6
|
||||
2-6,4-8"""
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"a,b,expected",
|
||||
[
|
||||
(Range(2, 4), Range(3, 4), True),
|
||||
(Range(2, 4), Range(1, 4), True),
|
||||
(Range(2, 4), Range(2, 4), True),
|
||||
(Range(4, 4), Range(3, 4), True),
|
||||
(Range(2, 4), Range(3, 5), False),
|
||||
(Range(4, 4), Range(3, 3), False),
|
||||
],
|
||||
)
|
||||
def test_range_fully_contains(a: Range, b: Range, expected: bool) -> None:
|
||||
assert a.fully_contains(b) == expected
|
||||
assert b.fully_contains(a) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"a,b,expected",
|
||||
[
|
||||
(Range(5, 7), Range(7, 9), True),
|
||||
(Range(2, 8), Range(3, 7), True),
|
||||
(Range(6, 6), Range(4, 6), True),
|
||||
(Range(2, 6), Range(4, 8), True),
|
||||
(Range(5, 7), Range(8, 9), False),
|
||||
(Range(2, 3), Range(5, 7), False),
|
||||
(Range(6, 6), Range(4, 5), False),
|
||||
(Range(2, 2), Range(4, 4), False),
|
||||
],
|
||||
)
|
||||
def test_range_overlaps(a: Range, b: Range, expected: bool) -> None:
|
||||
assert a.overlaps(b) == expected
|
||||
assert b.overlaps(a) == expected
|
||||
|
||||
|
||||
def test_part1(example: str) -> None:
|
||||
assert part1(example) == 2
|
||||
|
||||
|
||||
def test_part2(example: str) -> None:
|
||||
assert part2(example) == 4
|
||||
Reference in New Issue
Block a user