diff --git a/README.md b/README.md index 922ebb2f..eb6e561c 100644 --- a/README.md +++ b/README.md @@ -21,4 +21,4 @@ Development occurs in language-specific directories: |[Day14.hs](hs/src/Day14.hs)|[Day14.kt](kt/aoc2024-lib/src/commonMain/kotlin/com/github/ephemient/aoc2024/Day14.kt)|[day14.py](py/aoc2024/day14.py)|[day14.rs](rs/src/day14.rs)| |[Day15.hs](hs/src/Day15.hs)|[Day15.kt](kt/aoc2024-lib/src/commonMain/kotlin/com/github/ephemient/aoc2024/Day15.kt)|[day15.py](py/aoc2024/day15.py)|[day15.rs](rs/src/day15.rs)| |[Day16.hs](hs/src/Day16.hs)|[Day16.kt](kt/aoc2024-lib/src/commonMain/kotlin/com/github/ephemient/aoc2024/Day16.kt)|[day16.py](py/aoc2024/day16.py)|[day16.rs](rs/src/day16.rs)| -|[Day17.hs](hs/src/Day17.hs)|[Day17.kt](kt/aoc2024-lib/src/commonMain/kotlin/com/github/ephemient/aoc2024/Day17.kt)||| +|[Day17.hs](hs/src/Day17.hs)|[Day17.kt](kt/aoc2024-lib/src/commonMain/kotlin/com/github/ephemient/aoc2024/Day17.kt)|[day17.py](py/aoc2024/day17.py)|| diff --git a/py/aoc2024/day17.py b/py/aoc2024/day17.py new file mode 100644 index 00000000..3d1d8bb9 --- /dev/null +++ b/py/aoc2024/day17.py @@ -0,0 +1,100 @@ +""" +Day 17: Chronospatial Computer +""" + +import re +import sys +from typing import Generator + +SAMPLE_INPUT_1 = """ +Register A: 729 +Register B: 0 +Register C: 0 + +Program: 0,1,5,4,3,0 +""" +SAMPLE_INPUT_2 = """ +Register A: 2024 +Register B: 0 +Register C: 0 + +Program: 0,3,5,4,3,0 +""" + +_pattern = re.compile(r"A: (\d+)|B: (\d+)|C: (\d+)|Program: ([\d,]*)") + + +def _parse(data: str) -> tuple[int, int, int, list[int]]: + a, b, c, program = 0, 0, 0, [] + for match in _pattern.finditer(data): + if capture := match.group(1): + a = int(capture) + elif capture := match.group(2): + b = int(capture) + elif capture := match.group(3): + c = int(capture) + else: + program = [int(num) for num in match.group(4).split(",")] + return a, b, c, program + + +def _run(a: int, b: int, c: int, program: list[int]) -> Generator[int]: + ip = 0 + while 0 <= ip and ip + 1 < len(program): + instruction = program[ip] + operand = program[ip + 1] + combo = (0, 1, 2, 3, a, b, c)[operand] if 0 <= operand < 7 else None + match instruction: + case 0: + a = a >> combo + case 1: + b = b ^ operand + case 2: + b = combo & 7 + case 3: + if a: + ip = operand + continue + case 4: + b = b ^ c + case 5: + yield combo & 7 + case 6: + b = a >> combo + case 7: + c = a >> combo + case _: + raise NotImplementedError() + ip += 2 + + +def part1(data: str) -> str: + """ + >>> part1(SAMPLE_INPUT_1) + '4,6,3,5,6,3,5,2,1,0' + """ + a, b, c, program = _parse(data) + return ",".join(map(str, _run(a, b, c, program))) + + +def part2(data: str) -> int: + """ + >>> part2(SAMPLE_INPUT_2) + 117440 + """ + a, b, c, program = _parse(data) + expected = ",".join(map(str, program)) + candidates = [0] + while candidates: + keys = [8 * a + b for a in candidates for b in range(8)] + candidates.clear() + for a in keys: + value = ",".join(map(str, _run(a, b, c, program))) + if value == expected: + return a + if expected.endswith("," + value): + candidates.append(a) + return None + + +parts = (part1, part2) diff --git a/py/pyproject.toml b/py/pyproject.toml index 9397ae21..635c44ee 100644 --- a/py/pyproject.toml +++ b/py/pyproject.toml @@ -40,6 +40,7 @@ day13 = "aoc2024.day13:parts" day14 = "aoc2024.day14:parts" day15 = "aoc2024.day15:parts" day16 = "aoc2024.day16:parts" +day17 = "aoc2024.day17:parts" [build-system] requires = ["poetry-core"]