From 9364a582c389843b47540d3b31dd025a7cd1ad9f Mon Sep 17 00:00:00 2001 From: Christoph Stahl Date: Fri, 8 Dec 2023 11:42:28 +0100 Subject: [PATCH] solution Day 08 --- aoc/aoc.py | 4 ++-- aoc/day08.py | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 aoc/day08.py diff --git a/aoc/aoc.py b/aoc/aoc.py index 0da2258..39ea1ff 100644 --- a/aoc/aoc.py +++ b/aoc/aoc.py @@ -182,7 +182,7 @@ class AocParseLinesSameParser(Generic[T1], AocParseLines[T1, T1]): def run_latest() -> None: days = [ import_module(f".{file.stem}", package="aoc") - for file in Path("aoc").iterdir() + for file in sorted(Path("aoc").iterdir()) if file.name.endswith(".py") and file.name.startswith("day") ] day = days[-1] @@ -193,7 +193,7 @@ def run_latest() -> None: def main() -> None: days = [ import_module(f".{file.stem}", package="aoc") - for file in Path("aoc").iterdir() + for file in sorted(Path("aoc").iterdir()) if file.name.endswith(".py") and file.name.startswith("day") ] for day in days: diff --git a/aoc/day08.py b/aoc/day08.py new file mode 100644 index 0000000..bc5a2b7 --- /dev/null +++ b/aoc/day08.py @@ -0,0 +1,68 @@ +from __future__ import annotations +import itertools +import math +from typing import Optional + +from .aoc import Aoc2, AocSameParser + + +class Map: + def __init__(self, inpt: str) -> None: + lines = inpt.splitlines() + + self.directions: list[int] = [0 if rl == "L" else 1 for rl in lines[0]] + + self.rules = {line[:3]: (line[7:10], line[12:15]) for line in lines[2:]} + + +class Day08(AocSameParser[Map], Aoc2[Map, Map]): + def parseinput(self, inpt: str) -> Map: + return Map(inpt) + + def part1(self, inpt: Map) -> int: + current = "AAA" + + for steps, direction in enumerate(itertools.cycle(inpt.directions)): + if current == "ZZZ": + return steps + else: + current = inpt.rules[current][direction] + raise RuntimeError("End of the rainbow") + + def find_loop(self, list_of_nrs: list[int]) -> Optional[tuple[int, int]]: + for index in range(len(list_of_nrs)): + for index in range(len(list_of_nrs[index + 1 :])): + diff = list_of_nrs[index + 1] - list_of_nrs[index] + if list_of_nrs[index + 1] + diff in list_of_nrs: + return list_of_nrs[index], diff + return None + + def part2(self, inpt: Map) -> int: + current = [node for node in inpt.rules.keys() if node.endswith("A")] + steps_to_z = [[] for _ in current] + loops: list[Optional[tuple[int, int]]] = [None for _ in current] + + for steps, direction in enumerate(itertools.cycle(inpt.directions)): + for n, node in enumerate(current): + if node.endswith("Z"): + steps_to_z[n].append(steps) + loops[n] = self.find_loop(steps_to_z[n]) + + if not any(loop is None for loop in loops): + break + else: + current = [inpt.rules[node][direction] for node in current] + + # Turns out, each path directly loops :/ + return math.lcm(*(loop[0] for loop in loops if loop is not None)) + + +def main() -> None: + day08 = Day08(2023, 8) + day08._example_code_nr1 = 1 + day08._example_code_nr2 = -1 + day08.run() + + +if __name__ == "__main__": + main()