diff --git a/2024/d21.py b/2024/d21.py index 30be74d..af13d5e 100644 --- a/2024/d21.py +++ b/2024/d21.py @@ -1,161 +1,110 @@ import heapq from lib import get_data -from functools import cache data = get_data(__file__) def new_dir(current, move): - if current == "<": - if move == ">": return "v" - else: return None - elif current == "^": - if move == ">": return "A" - elif move == "v": return "v" - else: return None - elif current == "v": - if move == "<": return "<" - elif move == "^": return "^" - elif move == ">": return ">" - else: return None - elif current == ">": - if move == "<": return "v" - elif move == "^": return "A" - else: return None - elif current == "A": - if move == "<": return "^" - elif move == "v": return ">" - else: return None - else: - assert False + return {"<": + {">": "v"}, + "^": + {">": "A", "v": "v"}, + "v": + {"<": "<", ">": ">", "^": "^"}, + ">": + {"^": "A", "<": "v"}, + "A": + {"<": "^", "v": ">"} + }[current].get(move, None) def new_num(current, move): - if current == "7": - if move == ">": return "8" - elif move == "v": return "4" - else: return None - elif current == "8": - if move == "<": return "7" - elif move == ">": return "9" - elif move == "v": return "5" - else: return None - elif current == "9": - if move == "<": return "8" - elif move == "v": return "6" - else: return None - elif current == "5": - if move == "^": return "8" - elif move == "<": return "4" - elif move == ">": return "6" - elif move == "v": return "2" - else: assert False - elif current == "4": - if move == "^": return "7" - elif move == ">": return "5" - elif move == "v": return "1" - else: return None - elif current == "6": - if move == "^": return "9" - elif move == "<": return "5" - elif move == "v": return "3" - else: return None - elif current == "1": - if move == "^": return "4" - elif move == ">": return "2" - else: return None - elif current == "2": - if move == "^": return "5" - elif move == "<": return "1" - elif move == ">": return "3" - elif move == "v": return "0" - else: assert False - elif current == "3": - if move == "^": return "6" - elif move == "<": return "2" - elif move == "v": return "A" - else: return None - elif current == "0": - if move == "^": return "2" - elif move == ">": return "A" - else: return None - elif current == "A": - if move == "^": return "3" - elif move == "<": return "0" - else: return None - else: - assert False + return {"7": {">": "8", "v": "4",}, + "8": {"<": "7", ">": "9", "v": "5"}, + "9": {"<": "8", "v": "6"}, + "4": {"^": "7", ">": "5", "v": "1"}, + "5": {"^": "8", ">": "6", "<": "4", "v": "2"}, + "6": {"^": "9", "<": "5", "v": "3"}, + "1": {"^": "4", ">": "2"}, + "2": {"^": "5", ">": "3", "<": "1", "v": "0"}, + "3": {"^": "6", "<": "2", "v": "A"}, + "0": {"^": "2", ">": "A"}, + "A": {"^": "3", "<": "0"}, + }[current].get(move, None) -@cache -def new_state(press, node): - cost, r, n, path = node - r = list(r) +def new_state(press, node, has_numpad): + key_pads, num_pad, typed, pressed = node + key_pads = list(key_pads) + pressed = pressed + press - for i in range(len(r)): + for i in range(len(key_pads)): if press == "A": - press = r[i] + press = key_pads[i] else: - r[i] = new_dir(r[i], press) - if r[i] is None: + key_pads[i] = new_dir(key_pads[i], press) + if key_pads[i] is None: return None press = None break - npath = str(path) if press is not None: - if press == "A": - npath += n + if has_numpad: + if press == "A": + typed = typed + num_pad + else: + num_pad = new_num(num_pad, press) + if num_pad is None: + return None else: - n = new_num(n, press) - if n is None: - return None - - nnode = cost + 1, tuple(r), n, npath + typed = typed + press + nnode = tuple(key_pads), num_pad, typed, pressed return nnode - if press == "A": - if r[0] == "A": - if r[1] == "A": - path += n - else: - n = new_num(n, r[1]) - else: - r[1] = new_dir(r[1], r[0]) - else: - r[0] = new_dir(r[0], press) + # if press == "A": + # if key_pads[0] == "A": + # if key_pads[1] == "A": + # entered += num_pad + # else: + # num_pad = new_num(num_pad, key_pads[1]) + # else: + # key_pads[1] = new_dir(key_pads[1], key_pads[0]) + # else: + # key_pads[0] = new_dir(key_pads[0], press) + # + # if key_pads[0] is None or key_pads[1] is None or num_pad is None: + # return None + # return tuple(key_pads), num_pad, typed - if r[0] is None or r[1] is None or n is None: - return None +def find(code, key_pads=2, has_numpad=False): + _, _, TYPED, ENTERED = 0, 1, 2, 3 - npath = str(path) - return cost + 1, tuple(r), n, npath - -def sequence(line): def dist(n1, n2): - """cost from node to node""" + if n1 == 0: return 0 return 1 def h(node): - """heuristic function (never overestimate)""" - return len(line) - len(node[3]) + return len(code) - len(node[TYPED]) def is_goal(node): - return node[3] == line + return node[TYPED] == code def neighbors(node): nbs = [] for move in '<^v>A': - nb = new_state(move, node) + nb = new_state(move, node, has_numpad) if nb is None: continue - if not line.startswith(node[3]): + if not code.startswith(nb[TYPED]): continue nbs.append(nb) return nbs - key_pads = tuple(['A' for _ in range(5)]) - start = (0, key_pads, 'A', '') + def hash(node): + return node[:-1] + + key_pads = tuple(['A' for _ in range(key_pads)]) + start = (key_pads, 'A', '', '') starts = [start] open_set = [] g_score = {} @@ -163,27 +112,32 @@ def sequence(line): for start in starts: heapq.heappush(open_set, (h(start), start)) - g_score[start] = dist(0, start) + g_score[hash(start)] = dist(0, start) while open_set: current_f_score, current = heapq.heappop(open_set) if is_goal(current): - assert current_f_score == g_score[current] - cost = g_score[current] + assert current_f_score == g_score[hash(current)] + # print(current) + cost = g_score[hash(current)] + cost = current[ENTERED] break + # print(current) for neighbor in neighbors(current): - tentative_g_score = g_score[current] + dist(current, neighbor) - if neighbor not in g_score or tentative_g_score < g_score[neighbor]: - g_score[neighbor] = tentative_g_score - f_score = g_score[neighbor] + h(neighbor) + tentative_g_score = g_score[hash(current)] + dist(current, neighbor) + if tentative_g_score < g_score.get(hash(neighbor), 10**15): + # print(" ", neighbor, end="") + # input() + g_score[hash(neighbor)] = tentative_g_score + f_score = g_score[hash(neighbor)] + h(neighbor) heapq.heappush(open_set, (f_score, neighbor)) return cost t = 0 for line in data.splitlines(): - s = sequence(line) - i = int("".join([c for c in line if ord('0') <= ord(c) <= ord('9')])) - print(s, i) - t += (s - 1) * i + s = find(line, 2, True) + i = int("".join([c for c in line if c.isdigit()])) + s = len(s) + t += s * i print(t)