From 3979a60fa491095022d2f34bda380a73e08938bd Mon Sep 17 00:00:00 2001 From: felixm Date: Sat, 21 Dec 2024 22:37:40 -0500 Subject: [PATCH] 2024 d21 wip that is not going anywhere --- 2024/d21.py | 200 +++++++++++++++++++++++++++++++++++----------------- 1 file changed, 137 insertions(+), 63 deletions(-) diff --git a/2024/d21.py b/2024/d21.py index 30be74d..ed6ba6a 100644 --- a/2024/d21.py +++ b/2024/d21.py @@ -1,9 +1,12 @@ import heapq from lib import get_data -from functools import cache - data = get_data(__file__) +data = """029A +980A +179A +456A +379A""" def new_dir(current, move): @@ -87,79 +90,43 @@ def new_num(current, move): assert False -@cache -def new_state(press, node): - cost, r, n, path = node - r = list(r) +def find_numpad(target: str) -> str: + COUNT, CURRENT, RESULT, PATH = 0, 1, 2, 3 - for i in range(len(r)): - if press == "A": - press = r[i] - else: - r[i] = new_dir(r[i], press) - if r[i] is None: - return None - press = None - break - - npath = str(path) - if press is not None: - if press == "A": - npath += n - else: - n = new_num(n, press) - if n is None: - return None - - nnode = cost + 1, tuple(r), n, npath - 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 r[0] is None or r[1] is None or n is None: - return None - - 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(target) - len(node[RESULT]) def is_goal(node): - return node[3] == line + return node[RESULT] == target def neighbors(node): nbs = [] - for move in '<^v>A': - nb = new_state(move, node) - if nb is None: + for press in 'A^>v<': + nnode = list(node) + nnode[PATH] = node[PATH] + press + if press == "A": + nnode[RESULT] = node[RESULT] + node[CURRENT] + else: + nnode[CURRENT] = new_num(node[CURRENT], press) + if nnode[CURRENT] is None: + continue + if not target.startswith(nnode[RESULT]): continue - if not line.startswith(node[3]): - continue - nbs.append(nb) + nnode[COUNT] += 1 + nbs.append(tuple(nnode)) return nbs - key_pads = tuple(['A' for _ in range(5)]) - start = (0, key_pads, 'A', '') + start = (0, 'A', '', '') starts = [start] open_set = [] g_score = {} cost = None + solutions = [] for start in starts: heapq.heappush(open_set, (h(start), start)) @@ -170,7 +137,90 @@ def sequence(line): if is_goal(current): assert current_f_score == g_score[current] cost = g_score[current] - break + cost = current[PATH] + if solutions != [] and len(cost) > len(solutions[-1]): + break + solutions.append(current[PATH]) + + 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) + heapq.heappush(open_set, (f_score, neighbor)) + return solutions + +def find_dirpad(target: str) -> str: + COUNT, CURRENT, RESULT, PATH = 0, 1, 2, 3 + + def dist(n1, n2): + if n1 == 0: + return 0 + return 1 + + def h(node): + hdist = { + "A": {"^": 2, ">": 2, "<": 4, "v": 3, "A": 1}, + ">": {"^": 3, ">": 1, "<": 3, "v": 2, "A": 2}, + "v": {"^": 2, ">": 2, "<": 2, "v": 1, "A": 3}, + "<": {"^": 3, ">": 3, "<": 1, "v": 2, "A": 4}, + "^": {"^": 1, ">": 3, "<": 3, "v": 2, "A": 2}, + } + d = 0 + cur_key = node[CURRENT] + nxt_key_idx = len(node[RESULT]) + while nxt_key_idx < len(target): + nxt_key = target[nxt_key_idx] + d += hdist[cur_key][nxt_key] + cur_key = nxt_key + nxt_key_idx += 1 + return d + + def is_goal(node): + return node[RESULT] == target + + def neighbors(node): + nbs = [] + to_press = list('A^>v<') + if node[PATH]: + to_press.remove(node[PATH][-1]) + to_press = [node[PATH][-1]] + to_press + + for press in to_press: + if press == "A": + new_result = node[RESULT] + node[CURRENT] + new_current = node[CURRENT] + if not target.startswith(new_result): + continue + else: + new_result = node[RESULT] + new_current = new_dir(node[CURRENT], press) + if new_current is None: + continue + nnode = (node[COUNT] + 1, new_current, new_result, node[PATH] + press) + nbs.append(nnode) + return nbs + + start = (0, 'A', '', '') + starts = [start] + open_set = [] + g_score = {} + cost = None + solutions = [] + + for start in starts: + heapq.heappush(open_set, (h(start), start)) + g_score[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] + cost = current[PATH] + if solutions and len(cost) > len(solutions[-1]): + break + solutions.append(cost) for neighbor in neighbors(current): tentative_g_score = g_score[current] + dist(current, neighbor) @@ -178,12 +228,36 @@ def sequence(line): g_score[neighbor] = tentative_g_score f_score = g_score[neighbor] + h(neighbor) heapq.heappush(open_set, (f_score, neighbor)) - return cost + return solutions + + +def find(a): + print(a) + a = find_numpad(a)[0] + print(len(a), a) + a = find_dirpad(a)[0] + print(len(a), a) + a = find_dirpad(a)[0] + print(len(a), a) + return a + + print(a) + for a in find_numpad(a): + print(len(a), a) + for a in find_dirpad(a): + print(len(a), a) + a = find_dirpad(a) + # print(len(a)) + # print() + exit() + # a = find_keypad(a) + return a t = 0 for line in data.splitlines(): - s = sequence(line) + s = find(line) i = int("".join([c for c in line if ord('0') <= ord(c) <= ord('9')])) - print(s, i) - t += (s - 1) * i + print(len(s), i, s) + # t += s * i + print(t)