Solve 2024:16 pt2 "Reindeer Maze"

Part 1 was easily solved by Dijkstras (using heapq).
Tricky part was to find the way to serialize the
queue items for the heappush and heappop to behave
as required.

Part 2 was incredible hard to figure out, and I
could not do it by myself. From the subreddit, it
is hinted that a traditional set of visited nodes
to determine algorithm exit would not work.

After some laboration and readin some hints, I
managed to find a solution where the code keeps
tracks using a defaultdict.
This commit is contained in:
Anders Englöf Ytterström 2024-12-17 17:04:01 +01:00
parent fbe6994e3a
commit 7c2b4c835a

View file

@ -1,85 +1,78 @@
import re
from collections import Counter, defaultdict, deque
from heapq import heappop, heappush
from itertools import chain, combinations, compress, permutations
from output import ADJ, DD, D, ccw, cw, ints, matrix, mdbg, mhd, vdbg
from output import D, matrix
def solve(data):
grid, H, W = matrix(data)
S = [(r, c) for r in range(H) for c in range(W) if grid[r][c] == "S"][0]
E = [(r, c) for r in range(H) for c in range(W) if grid[r][c] == "E"][0]
p1 = float('inf')
Q = deque([(0, (S,), 1)])
Q = [(0, S, 1)]
p1 = float("inf")
SE = dict()
ES = dict()
seen = set()
lowest = []
while Q:
cost, path, facing = heappop(Q)
pos = path[-1]
if (pos, facing) in seen:
continue
seen.add((pos, facing))
if pos == E:
p1 = min(cost, p1)
lowest.append(path)
cost, pos, dir = heappop(Q)
r, c = pos
for d, delta in enumerate(D):
if grid[r][c] == "#":
continue
if (r, c, dir) in seen:
continue
seen.add((r, c, dir))
if (r, c, dir) not in SE:
SE[(r, c, dir)] = cost
if pos == E:
p1 = min(p1, cost)
continue
for inc, delta, facing in [
(1, D[dir], dir),
(1000, (0, 0), (dir + 1) % 4),
(1000, (0, 0), (dir - 1) % 4),
]:
nc = cost + inc
dr, dc = delta
if grid[r + dr][c + dc] == "#":
continue
if abs(facing - d) == 2:
continue
if d != facing:
heappush(Q, (cost + 1000, path, d))
else:
heappush(Q, (cost + 1, path + ((r + dr, c + dc),), d))
return p1, None
heappush(Q, (nc, (r + dr, c + dc), facing))
Q = [(0, E, 0), (0, E, 1), (0, E, 2), (0, E, 3)]
seen = set()
while Q:
cost, pos, dir = heappop(Q)
r, c = pos
if grid[r][c] == "#":
continue
if (r, c, dir) in seen:
continue
seen.add((r, c, dir))
if (r, c, (dir + 2) % 4) not in ES:
ES[(r, c, (dir + 2) % 4)] = cost
if pos == S:
continue
for inc, delta, facing in [
(1, D[dir], dir),
(1000, (0, 0), (dir + 1) % 4),
(1000, (0, 0), (dir - 1) % 4),
]:
nc = cost + inc
dr, dc = delta
heappush(Q, (nc, (r + dr, c + dc), facing))
p2 = set()
for r in range(H):
for c in range(W):
for d in range(4):
if (
(r, c, d) in SE
and (r, c, d) in ES
and SE[(r, c, d)] + ES[(r, c, d)] == p1
):
p2.add((r, c))
return p1, len(p2)
if __name__ == "__main__":
import os
# use dummy data
inp = """
#################
#...#...#...#..E#
#.#.#.#.#.#.#.#.#
#.#.#.#...#...#.#
#.#.#.#.###.#.#.#
#...#.#.#.....#.#
#.#.#.#.#.#####.#
#.#...#.#.#.....#
#.#.#####.#.###.#
#.#.#.......#...#
#.#.###.#####.###
#.#.#...#.....#.#
#.#.#.#####.###.#
#.#.#.........#.#
#.#.#.#########.#
#S#.............#
#################
""".strip()
# uncomment to instead use stdin
# import sys; inp = sys.stdin.read().strip()
# uncomment to use AoC provided puzzle input
with open("./input/16.txt", "r") as f:
inp = f.read().strip()
# uncomment to do initial data processing shared by part 1-2
p1, p2 = solve(inp)
print(p1)
os.system(f"echo {p1} | wl-copy")
print(p2)
os.system(f"echo {p2} | wl-copy")
# uncomment and replace 0 with actual output to refactor code
# and ensure nonbreaking changes
# assert p1 == 0
# assert p2 == 0