diff --git a/2024/d21.py b/2024/d21.py index ed6ba6a..30be74d 100644 --- a/2024/d21.py +++ b/2024/d21.py @@ -1,12 +1,9 @@ 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): @@ -90,43 +87,79 @@ def new_num(current, move): assert False -def find_numpad(target: str) -> str: - COUNT, CURRENT, RESULT, PATH = 0, 1, 2, 3 +@cache +def new_state(press, node): + cost, r, n, path = node + r = list(r) + 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): - if n1 == 0: - return 0 + """cost from node to node""" return 1 def h(node): - return len(target) - len(node[RESULT]) + """heuristic function (never overestimate)""" + return len(line) - len(node[3]) def is_goal(node): - return node[RESULT] == target + return node[3] == line def neighbors(node): nbs = [] - 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]): + for move in '<^v>A': + nb = new_state(move, node) + if nb is None: continue - nnode[COUNT] += 1 - nbs.append(tuple(nnode)) + if not line.startswith(node[3]): + continue + nbs.append(nb) return nbs - start = (0, 'A', '', '') + key_pads = tuple(['A' for _ in range(5)]) + start = (0, key_pads, 'A', '') starts = [start] open_set = [] g_score = {} cost = None - solutions = [] for start in starts: heapq.heappush(open_set, (h(start), start)) @@ -137,90 +170,7 @@ def find_numpad(target: str) -> str: 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(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) + break for neighbor in neighbors(current): tentative_g_score = g_score[current] + dist(current, neighbor) @@ -228,36 +178,12 @@ def find_dirpad(target: str) -> str: g_score[neighbor] = tentative_g_score f_score = g_score[neighbor] + h(neighbor) heapq.heappush(open_set, (f_score, neighbor)) - 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 + return cost t = 0 for line in data.splitlines(): - s = find(line) + s = sequence(line) i = int("".join([c for c in line if ord('0') <= ord(c) <= ord('9')])) - print(len(s), i, s) - # t += s * i - + print(s, i) + t += (s - 1) * i print(t) diff --git a/2024/d25.py b/2024/d25.py new file mode 100644 index 0000000..b447d95 --- /dev/null +++ b/2024/d25.py @@ -0,0 +1,25 @@ +from lib import get_data +from lib import Grid2D + +data = get_data(__file__) + +locks = [] +keys = [] + +for o in data.split("\n\n"): + g = Grid2D(o) + if all(g[(0, c)] == "#" for c in range(g.n_cols)): + keys.append(g) + elif all(g[g.n_rows - 1, c] == "#" for c in range(g.n_cols)): + locks.append(g) + else: + assert False + +t = 0 +for key in keys: + for lock in locks: + k = key.find("#") + l = lock.find("#") + if len(k) + len(l) == len(set(k) | set(l)): + t += 1 +print(t)