From ce08d39469720115cfc416ece0f5049968bc9d10 Mon Sep 17 00:00:00 2001 From: Daniel Lin Date: Tue, 26 Dec 2023 17:54:38 -0500 Subject: [PATCH] Day 23: A Long Walk --- README.md | 2 +- py/aoc2023/day23.py | 136 ++++++++++++++++++++++++++++++++++++++++++++ py/pyproject.toml | 1 + 3 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 py/aoc2023/day23.py diff --git a/README.md b/README.md index 2707d8bb..0ef577f7 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,6 @@ Development occurs in language-specific directories: |[Day20.hs](hs/src/Day20.hs)|[Day20.kt](kt/aoc2023-lib/src/commonMain/kotlin/com/github/ephemient/aoc2023/Day20.kt)|[day20.py](py/aoc2023/day20.py)|[day20.rs](rs/src/day20.rs)| |[Day21.hs](hs/src/Day21.hs)|[Day21.kt](kt/aoc2023-lib/src/commonMain/kotlin/com/github/ephemient/aoc2023/Day21.kt)|[day21.py](py/aoc2023/day21.py)|[day21.rs](rs/src/day21.rs)| |[Day22.hs](hs/src/Day22.hs)|[Day22.kt](kt/aoc2023-lib/src/commonMain/kotlin/com/github/ephemient/aoc2023/Day22.kt)|[day22.py](py/aoc2023/day22.py)|[day22.rs](rs/src/day22.rs)| -|[Day23.hs](hs/src/Day23.hs)|[Day23.kt](kt/aoc2023-lib/src/commonMain/kotlin/com/github/ephemient/aoc2023/Day23.kt)||[day23.rs](rs/src/day23.rs)| +|[Day23.hs](hs/src/Day23.hs)|[Day23.kt](kt/aoc2023-lib/src/commonMain/kotlin/com/github/ephemient/aoc2023/Day23.kt)|[day23.py](py/aoc2023/day23.py)|[day23.rs](rs/src/day23.rs)| |[Day24.hs](hs/src/Day24.hs)|[Day24.kt](kt/aoc2023-lib/src/commonMain/kotlin/com/github/ephemient/aoc2023/Day24.kt)|[day24.py](py/aoc2023/day24.py)|[day24.rs](rs/src/day24.rs) ½| |[Day25.hs](hs/src/Day25.hs)|[Day25.kt](kt/aoc2023-lib/src/commonMain/kotlin/com/github/ephemient/aoc2023/Day25.kt)|[day25.py](py/aoc2023/day25.py)|[day25.rs](rs/src/day25.rs)| diff --git a/py/aoc2023/day23.py b/py/aoc2023/day23.py new file mode 100644 index 00000000..d972401c --- /dev/null +++ b/py/aoc2023/day23.py @@ -0,0 +1,136 @@ +""" +Day 23: A Long Walk +""" + +SAMPLE_INPUT = """ +#.##################### +#.......#########...### +#######.#########.#.### +###.....#.>.>.###.#.### +###v#####.#v#.###.#.### +###.>...#.#.#.....#...# +###v###.#.#.#########.# +###...#.#.#.......#...# +#####.#.#.#######.#.### +#.....#.#.#.......#...# +#.#####.#.#.#########v# +#.#...#...#...###...>.# +#.#.#v#######v###.###v# +#...#.>.#...>.>.#.###.# +#####v#.#.###v#.#.###.# +#.....#...#...#.#.#...# +#.#########.###.#.#.### +#...###...#...#...#.### +###.###.#.###v#####v### +#...#...#.#.>.>.#.>.### +#.###.###.#.###.#.#v### +#.....###...###...#...# +#####################.# +""" + + +# pylint: disable=too-many-locals +def _parse(data): + data = { + (y, x): c + for y, line in enumerate(data.splitlines()) + for x, c in enumerate(line) + if c in ".<>^v" + } + start, end = min(data), max(data) + gr = { + (y, x): { + dst: ( + c.strip(".") in f and d.strip(".") in f, + c.strip(".") in b and d.strip(".") in b, + 1, + ) + for dst, f, b in zip( + [(y, x - 1), (y, x + 1), (y - 1, x), (y + 1, x)], "<>^v", ">^v" + } + for (y, x), c in data.items() + } + done = False + while not done: + done = True + for key in list(gr): + if key in (start, end): + continue + edges = gr[key] + if not edges: + del gr[key] + elif len(edges) == 1: + (key2,) = edges + del gr[key] + del gr[key2][key] + elif len(edges) == 2: + (key1, (f01, b01, w01)), (key2, (f02, b02, w02)) = edges.items() + f1, b1, w1 = gr[key1][key] + f2, b2, w2 = gr[key2][key] + del gr[key] + del gr[key1][key] + del gr[key2][key] + gr[key1][key2] = (f1 & f02, b1 & b02, w1 + w02) + gr[key2][key1] = (f2 & f01, b2 & b01, w2 + w01) + else: + continue + done = False + return ( + start, + end, + { + key: weights + for key, edges in gr.items() + if (weights := {dst: w for dst, (f, _, w) in edges.items() if f}) + }, + ) + + +def part1(data): + """ + >>> part1(SAMPLE_INPUT) + 94 + """ + start, end, gr = _parse(data) + + def go(pos, used, distance, best): + if pos == end: + return distance if best is None or distance > best else best + + reachable = {pos} + + def dfs(pos): + for dst in gr.get(pos, {}): + if dst not in used and dst not in reachable: + reachable.add(dst) + dfs(dst) + + dfs(pos) + potential = distance + sum( + max((w for dst, w in gr.get(dst, {}).items() if dst not in used), default=0) + for dst in reachable + ) + if best is not None and potential <= best or end not in reachable: + return best + + used = used | {pos} + for dst, w in gr[pos].items(): + if dst not in used: + best = go(dst, used, distance + w, best) + + return best + + return go(start, set(), 0, None) + + +def part2(data): + """ + >>> part2(SAMPLE_INPUT) + 154 + """ + return part1(data.translate(data.maketrans("<>^v", "...."))) + + +parts = (part1, part2) diff --git a/py/pyproject.toml b/py/pyproject.toml index 75bbf5d6..6f459e13 100644 --- a/py/pyproject.toml +++ b/py/pyproject.toml @@ -45,6 +45,7 @@ day19 = "aoc2023.day19:parts" day20 = "aoc2023.day20:parts" day21 = "aoc2023.day21:parts" day22 = "aoc2023.day22:parts" +day23 = "aoc2023.day23:parts" day24 = "aoc2023.day24:parts" day25 = "aoc2023.day25:parts"