Python: Day 3
This commit is contained in:
65
03/py/solution.py
Normal file
65
03/py/solution.py
Normal file
@@ -0,0 +1,65 @@
|
||||
from pathlib import Path
|
||||
from typing import Generator, TypeVar
|
||||
from collections.abc import Iterable
|
||||
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
def threes(i: Iterable[T]) -> Generator[tuple[T, T, T], None, None]:
|
||||
if len(i) % 3 != 0:
|
||||
raise ValueError()
|
||||
|
||||
for idx in range(0, len(i), 3):
|
||||
yield i[idx], i[idx + 1], i[idx + 2]
|
||||
|
||||
|
||||
def priority(s: str) -> int:
|
||||
assert len(s) == 1
|
||||
if "a" <= s <= "z":
|
||||
return ord(s) - ord("a") + 1
|
||||
elif "A" <= s <= "Z":
|
||||
return ord(s) - ord("A") + 27
|
||||
raise RuntimeError(f"Invalid char: {s}")
|
||||
|
||||
|
||||
def split(s: str) -> tuple[str, str]:
|
||||
mid = len(s) // 2
|
||||
return s[:mid], s[mid:]
|
||||
|
||||
|
||||
def part1(data: str) -> int:
|
||||
result = 0
|
||||
for line in data.splitlines():
|
||||
left, right = split(line)
|
||||
both = {ch for ch in right}.intersection({ch for ch in left})
|
||||
value = 0
|
||||
for ch in both:
|
||||
value += priority(ch)
|
||||
print(f"{line}: {both}")
|
||||
result += value
|
||||
return result
|
||||
|
||||
|
||||
def part2(data: str) -> int:
|
||||
result = 0
|
||||
lines = data.splitlines()
|
||||
for a, b, c in threes(lines):
|
||||
badge = set(a).intersection(set(b)).intersection(set(c))
|
||||
value = 0
|
||||
for ch in badge:
|
||||
value += priority(ch)
|
||||
result += value
|
||||
return result
|
||||
|
||||
|
||||
# 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}")
|
||||
57
03/py/test.py
Normal file
57
03/py/test.py
Normal file
@@ -0,0 +1,57 @@
|
||||
from typing import ContextManager, Optional
|
||||
from solution import part1, part2, priority, threes
|
||||
|
||||
from contextlib import nullcontext as no_raise
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def example_data() -> str:
|
||||
return """vJrwpWtwJgWrhcsFMMfFFhFp
|
||||
jqHRNqRjqzjGDLGLrsFMfFZSrLrFZsSL
|
||||
PmmdzqPrVvPwwTWBwg
|
||||
wMqvLMZHhHMvwLHjbvcjnnSBnvTQFn
|
||||
ttgJtRGJQctTZtZT
|
||||
CrZsJsPPZsGzwwsLwLmpwMDw"""
|
||||
|
||||
|
||||
def test_part1(example_data: str) -> None:
|
||||
assert part1(example_data) == 157
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"s,expected",
|
||||
[
|
||||
("a", 1),
|
||||
("b", 2),
|
||||
("z", 26),
|
||||
("A", 27),
|
||||
("C", 29),
|
||||
("Z", 52),
|
||||
],
|
||||
)
|
||||
def test_priority(s: str, expected: int) -> None:
|
||||
assert priority(s) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"line,expected,raises",
|
||||
[
|
||||
("a\nb\nc", [("a", "b", "c")], no_raise()),
|
||||
("a\nb\nc\nd", None, pytest.raises(ValueError)),
|
||||
],
|
||||
)
|
||||
def test_threes(
|
||||
line: str,
|
||||
expected: Optional[list[tuple[str, str, str]]],
|
||||
raises: ContextManager,
|
||||
):
|
||||
with raises:
|
||||
actual = list(threes(line.splitlines()))
|
||||
if expected:
|
||||
assert actual == expected
|
||||
|
||||
|
||||
def test_part2(example_data: str) -> None:
|
||||
assert part2(example_data) == 70
|
||||
Reference in New Issue
Block a user