diff --git a/2022-python/README.md b/2022-python/README.md new file mode 100644 index 0000000..2a5b22b --- /dev/null +++ b/2022-python/README.md @@ -0,0 +1,48 @@ +# Advent of Code 2022 + +Solutions for #aoc2022 in Python 3 (3.13.4). + +Programming setup: + +- Lenovo Thinkpad T14 +- OpenSUSE Tumbleweed with labwc +- Helix editor +- Vivaldi +- Foot + +## Help scripts + +Display all solved puzzles: + + python aoc.py + +To bootstrap a new puzzle (creates `input/.txt` and `output/day_.py`): + + python aoc.py new + +Manually copy the puzzle input from https://adventofcode.com and paste it in `input/.txt` +to start coding. + + wl-paste > input/.txt + +Solve separate puzzle (replace `XX` with the puzzle number): + + python -m output.day_XX + +Solve separate puzzle using stdin (replace `XX` with the puzzle number): + + wl-paste | python -m output.day_XX + cat tmpfile | python -m output.day_XX + +Execute separate puzzle on file save (replace `XX` with the puzzle number): + + ls output/*.py | entr -c -s 'wlpaste | python -m output.day_XX' + ls output/*.py | entr -c -s 'cat tmpfile | python -m output.day_XX' + ls output/*.py | entr -c -r python -m output.day_XX + +(requires `entr` and `wl-paste`, Mac users can instead use `pbpaste`. If you +prefer X at Linux, use `xclip -selection clipboard -o`). + +To lint files: + + ls output/*.py | entr -r -c flake8 output --ignore=E741,E501,E203 diff --git a/2022-python/aoc.py b/2022-python/aoc.py new file mode 100644 index 0000000..b606de2 --- /dev/null +++ b/2022-python/aoc.py @@ -0,0 +1,118 @@ +import sys +from pathlib import Path + + +def headline(n): + """Print day number and name, followed by a ruler. Used by the answer decorator""" + print(f"\nDay {int(n)} - https://adventofcode.com/{year}/day/{int(n)}\n") + + +year = 2022 +nostrip = [5] + +try: + _, day_no, *name = sys.argv +except ValueError: + day_no = None + name = None + +Path("./input").mkdir(parents=True, exist_ok=True) +Path("./output").mkdir(parents=True, exist_ok=True) + +if day_no and name: + name = " ".join(name) + padded_no = day_no.zfill(2) + with open("output/day_{}.py".format(padded_no), "w") as s: + s.write( + f""" +import re +from collections import deque, Counter, defaultdict +from heapq import heappop, heappush +from itertools import compress, combinations, chain, permutations + +from output import matrix, D, DD, ADJ, ints, mhd, mdbg, vdbg, cw, ccw, bk + + +def solve(data): + p1 = None + p2 = None + return p1, p2 + + +if __name__ == "__main__": + import os + + # use dummy data + inp = \"\"\" + replace me + \"\"\".strip() + + # uncomment to instead use stdin + # import sys; inp = sys.stdin.read().strip() + + # uncomment to use AoC provided puzzle input + # with open("./input/{padded_no}.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 +""".strip() + + "\n" + ) + exit(0) + +print( + f"\n\033[95m\033[1mAdvent of Code {year}\033[0m" + "\n###################" + "\n\n\033[96mby Anders Englöf Ytterström\033[0m" +) + + +stars = 0 +for i in [str(n).zfill(2) for n in range(1, 26)]: + if not day_no or day_no.zfill(2) == i: + try: + day = __import__( + "output.day_{}".format(i), + globals(), + locals(), + ["solve"], + 0, + ) + with open(f"./input/{i}.txt", "r") as f: + data = f.read() + if int(i) not in nostrip: + data = data.strip() + headline(i) + try: + data = day.presolve(data) + except AttributeError: + pass + try: + p1, p2 = day.solve(data) + except AttributeError: + pass + if p1: + print(f" \033[92m1)\033[0m {p1}") + stars += 1 + if p2: + print(f" \033[92m2)\033[0m {p2}") + stars += 1 + except IOError: + pass + except ImportError: + pass +if not day_no: + print(f"\nStars: {stars}") + print("".join("*" if n < stars else "•" for n in range(50))) +print("") diff --git a/2022-python/output/__init__.py b/2022-python/output/__init__.py new file mode 100644 index 0000000..809c3f5 --- /dev/null +++ b/2022-python/output/__init__.py @@ -0,0 +1,201 @@ +import re + +# Directions/Adjacents for 2D matrices, in the order UP, RIGHT, DOWN, LEFT +D = [ + (-1, 0), + (0, 1), + (1, 0), + (0, -1), +] + +Di = [ + (-1, -1), + (-1, 1), + (1, -1), + (1, 1), +] + +# Directions for 2D matrices, as a dict with keys U, R, D, L +DD = { + "U": (-1, 0), + "R": (0, 1), + "D": (1, 0), + "L": (0, -1), +} + +DDa = { + "^": (-1, 0), + ">": (0, 1), + "v": (1, 0), + "<": (0, -1), +} + +# Adjacent relative positions including diagonals for 2D matrices, in the order NW, N, NE, W, E, SW, S, SE +ADJ = [ + (-1, -1), + (-1, 0), + (1, -1), + (0, -1), + (0, 1), + (1, 1), + (1, 0), + (1, -1), +] + + +def ints(s): + """Extract all integers from a string""" + return [int(n) for n in re.findall(r"\d+", s)] + + +def sints(s): + """Extract all signed integers from a string""" + return [int(n) for n in re.findall(r"-?\d+", s)] + + +def mhd(a, b): + """Calculates the Manhattan distance between 2 positions in the format (y, x) or (x, y)""" + ar, ac = a + br, bc = b + return abs(ar - br) + abs(ac - bc) + + +def matrix(d): + """Transform a string into an iterable matrix. Returns the matrix, row count and col count""" + m = [tuple(r) for r in d.split()] + return m, len(m), len(m[0]) + + +def mdbg(m): + """Print-debug a matrix""" + for r in m: + print("".join(r)) + + +def vdbg(seen, h, w): + """Print-debug visited positions of a matrix""" + for r in range(h): + print("".join(["#" if (r, c) in seen else " " for c in range(w)])) + + +def vvdbg(seen, h, w): + """Print-debug visited positions of a matrix, with values""" + for r in range(h): + print("".join([seen[(r, c)] if (r, c) in seen else "." for c in range(w)])) + + +def cw(y, x): + """Flip a (y, x) direction counterwise: U->R, R->D, D->L, L->U. + + >>> cw(-1, 0) + (0, 1) + >>> cw(0, 1) + (1, 0) + >>> cw(1, 0) + (0, -1) + >>> cw(0, -1) + (-1, 0) + """ + return (x, y) if y == 0 else (x, -y) + + +def ccw(y, x): + """Flip a (y, x) direction counterwise: U->L, L->D, D->R, R->U. + + >>> ccw(-1, 0) + (0, -1) + >>> ccw(0, -1) + (1, 0) + >>> ccw(1, 0) + (0, 1) + >>> ccw(0, 1) + (-1, 0) + """ + return (x, y) if x == 0 else (-x, y) + + +def bfs(S, E=None): + """BFS algorithm, equal weighted nodes""" + seen = set() + q = [(S, 0)] + g = {} # graph, required to be provided at some point + while q: + m, w = q.pop(0) + if m in seen: + continue + seen.add(m) + # investigate here + for s in g[m]: + q.append((s, w + 1)) + # return insights + + +def mhd_search(r, c, R=20): + """returns all coords that are within R manhattan distance from (r,c)""" + p = set() + for d in range(1, R + 1): + p.add((r, c + d)) + p.add((r, c - d)) + p.add((r + d, c)) + p.add((r - d, c)) + for dd in range(d): + p.add((r - dd, c - d + dd)) + p.add((r + dd, c - d + dd)) + p.add((r - dd, c - dd + d)) + p.add((r + dd, c - dd + d)) + return p + + +def dijkstras(grid, start, target): + """ + 1. Create an array that holds the distance of each vertex from the starting + vertex. Initially, set this distance to infinity for all vertices except + the starting vertex which should be set to 0. + 2. Create a priority queue (heap) and insert the starting vertex with its + distance of 0. + 3. While there are still vertices left in the priority queue, select the vertex + with the smallest recorded distance from the starting vertex and visit its + neighboring vertices. + 4. For each neighboring vertex, check if it is visited already or not. If it + isn’t visited yet, calculate its tentative distance by adding its weight + to the smallest distance found so far for its parent/previous node + (starting vertex in case of first-level vertices). + 5. If this tentative distance is smaller than previously recorded value + (if any), update it in our ‘distances’ array. + 6. Finally, add this visited vertex with its updated distance to our priority + queue and repeat step-3 until we have reached our destination or exhausted + all nodes. + """ + import heapq + + target = max(grid) + seen = set() + queue = [(start, 0)] + while queue: + cost, pos, direction, steps = heapq.heappop(queue) + y, x = pos + dy, dx = direction + + if pos == target: + return cost + + if ((pos, "and stuff")) in seen: + continue + + seen.add((pos, "and stuff")) + + neighbors = [] + for n in neighbors: + heapq.heappush(queue, ("stuffs")) + + return -1 + + +def bk(graph, p, r=set(), x=set()): + """Bron-Kerbosch algoritm, no pivot: https://en.wikipedia.org/wiki/Bron%E2%80%93Kerbosch_algorithm""" + if not p and not x: + yield r + while p: + v = p.pop() + yield from bk(graph, p & set(graph[v]), r | {v}, x & graph[v]) + x.add(v) diff --git a/2022-python/output/day_01.py b/2022-python/output/day_01.py new file mode 100644 index 0000000..f637ccf --- /dev/null +++ b/2022-python/output/day_01.py @@ -0,0 +1,26 @@ +from output import ints + + +def solve(data): + E = data.split("\n\n") + p1 = 0 + C = [] + for e in E: + e = sum(ints(e)) + p1 = max(p1, e) + C.append(e) + p2 = sum(sorted(C, reverse=True)[:3]) + return p1, p2 + + +if __name__ == "__main__": + with open("./input/01.txt", "r") as f: + inp = f.read().strip() + + p1, p2 = solve(inp) + + print(p1) + print(p2) + + assert p1 == 66616 + assert p2 == 199172 diff --git a/2022-python/output/day_02.py b/2022-python/output/day_02.py new file mode 100644 index 0000000..b58799e --- /dev/null +++ b/2022-python/output/day_02.py @@ -0,0 +1,40 @@ +def solve(data): + S1 = { + "A X": 1 + 3, + "A Y": 2 + 6, + "A Z": 3 + 0, + "B X": 1 + 0, + "B Y": 2 + 3, + "B Z": 3 + 6, + "C X": 1 + 6, + "C Y": 2 + 0, + "C Z": 3 + 3, + } + S2 = { + "A X": 3 + 0, + "A Y": 1 + 3, + "A Z": 2 + 6, + "B X": 1 + 0, + "B Y": 2 + 3, + "B Z": 3 + 6, + "C X": 2 + 0, + "C Y": 3 + 3, + "C Z": 1 + 6, + } + R = data.splitlines() + p1 = sum(S1[r] for r in R) + p2 = sum(S2[r] for r in R) + return p1, p2 + + +if __name__ == "__main__": + with open("./input/02.txt", "r") as f: + inp = f.read().strip() + + p1, p2 = solve(inp) + + print(p1) + print(p2) + + assert p1 == 12772 + assert p2 == 11618 diff --git a/2022-python/output/day_03.py b/2022-python/output/day_03.py new file mode 100644 index 0000000..05a3011 --- /dev/null +++ b/2022-python/output/day_03.py @@ -0,0 +1,28 @@ +def solve(data): + rs = data.split() + p1 = sum(map(_p1, rs)) + p2 = sum(map(_p2, zip(rs[::3], rs[1::3], rs[2::3]))) + return p1, p2 + + +def _p1(rs): + c = (set(rs[: len(rs) // 2]) & set(rs[len(rs) // 2 :])).pop() + return ord(c) - 96 if c.islower() else ord(c) - 38 + + +def _p2(rsg): + c = (set(rsg[0]) & set(rsg[1]) & set(rsg[2])).pop() + return ord(c) - 96 if c.islower() else ord(c) - 38 + + +if __name__ == "__main__": + with open("./input/03.txt", "r") as f: + inp = f.read().strip() + + p1, p2 = solve(inp) + + print(p1) + print(p2) + + assert p1 == 7817 + assert p2 == 2444 diff --git a/2022-python/output/day_04.py b/2022-python/output/day_04.py new file mode 100644 index 0000000..7420aa9 --- /dev/null +++ b/2022-python/output/day_04.py @@ -0,0 +1,31 @@ +from output import ints + + +def solve(data): + p1 = sum(_p1(line) for line in data.split()) + p2 = sum(_p2(line) for line in data.split()) + return p1, p2 + + +def _p1(data): + a1, a2, b1, b2 = ints(data) + return (a1 >= b1 and a2 <= b2) or (b1 >= a1 and b2 <= a2) + + +def _p2(data): + a1, a2, b1, b2 = ints(data) + + return len(set(range(a1, a2 + 1)) & set(range(b1, b2 + 1))) > 0 + + +if __name__ == "__main__": + with open("./input/04.txt", "r") as f: + inp = f.read().strip() + + p1, p2 = solve(inp) + + print(p1) + print(p2) + + assert p1 == 507 + assert p2 == 897 diff --git a/2022-python/output/day_05.py b/2022-python/output/day_05.py new file mode 100644 index 0000000..cb33dd6 --- /dev/null +++ b/2022-python/output/day_05.py @@ -0,0 +1,45 @@ +from copy import deepcopy +from collections import defaultdict + +from output import ints + + +def solve(data): + state, program = data.split("\n\n") + labels, *state = state.splitlines()[::-1] + num = max(ints(labels)) + stacks = defaultdict(list) + for r, text in enumerate(state): + for c in range(4 * num): + if c % 4 == 1: + if text[c] != " ": + stacks[c // 4 + 1].append(text[c]) + p1 = _act(program, deepcopy(stacks)) + p2 = _act(program, deepcopy(stacks), r=9001) + return p1, p2 + + +def _act(program, stacks, r=9000): + for line in program.splitlines(): + count, old, new = ints(line) + match r: + case 9000: + for _ in range(count): + stacks[new].append(stacks[old].pop()) + case 9001: + stacks[new] += stacks[old][-count:] + stacks[old] = stacks[old][:-count] + return "".join(s[-1] for s in stacks.values()) + + +if __name__ == "__main__": + with open("./input/05.txt", "r") as f: + inp = f.read() + + p1, p2 = solve(inp) + + print(p1) + print(p2) + + assert p1 == "ZRLJGSCTR" + assert p2 == "PRTTGRFPB" diff --git a/2022-python/output/day_06.py b/2022-python/output/day_06.py new file mode 100644 index 0000000..715ec09 --- /dev/null +++ b/2022-python/output/day_06.py @@ -0,0 +1,21 @@ +def solve(data): + for p1 in range(4, len(data)): + if len(set(data[p1 - 4 : p1])) == 4: + break + for p2 in range(14, len(data)): + if len(set(data[p2 - 14 : p2])) == 14: + break + return p1, p2 + + +if __name__ == "__main__": + with open("./input/06.txt", "r") as f: + inp = f.read().strip() + + p1, p2 = solve(inp) + + print(p1) + print(p2) + + assert p1 == 1238 + assert p2 == 3037 diff --git a/2022-python/output/day_07.py b/2022-python/output/day_07.py new file mode 100644 index 0000000..60f30c8 --- /dev/null +++ b/2022-python/output/day_07.py @@ -0,0 +1,54 @@ +from collections import defaultdict + +from output import ints + + +def solve(data): + fs = defaultdict(int) + pwd = [] + for cmd in data.splitlines(): + if cmd.startswith("$ ls") or cmd.startswith("dir "): + continue + if cmd.startswith("$ cd"): + d = cmd.split()[-1] + match d: + case "/": + pwd = [] + case "..": + pwd.pop() + case _: + pwd.append(d) + continue + a = "" + w = sum(ints(cmd)) + # in example input, all directories have distinct names. + # + # the puzzle input have repeated directory names, e.g. both + # /d/a and /c/b/a (a subfolder named "a") exists. + # + # Furthermore, puzzle input also has cases of /a/b/a, + # e.g. a recurring across directory path. + # + # Lost several hours due to this. + fs[a] += w + for p in pwd: + a += f"/{p}" + fs[a] += w + p1 = sum(i for i in fs.values() if i <= 100_000) + free = 70_000_000 - fs[""] + needed = 30_000_000 - free + p2 = min(i for i in fs.values() if i >= needed) + return p1, p2 + + +if __name__ == "__main__": + with open("./input/07.txt", "r") as f: + inp = f.read().strip() + + p1, p2 = solve(inp) + + print(p1) + print(p2) + + assert p1 == 1644735 + assert p2 == 1300850 diff --git a/2022-python/output/day_08.py b/2022-python/output/day_08.py new file mode 100644 index 0000000..d8906f8 --- /dev/null +++ b/2022-python/output/day_08.py @@ -0,0 +1,60 @@ +from math import prod + +from output import matrix + + +def solve(data): + M, R, C = matrix(data) + p1 = 2 * R + 2 * C - 4 + p2 = 0 + for row in range(1, R - 1): + for col in range(1, C - 1): + h = int(M[row][col]) + visible = 0 + score = [] + + for i, dy in enumerate(range(row - 1, -1, -1), start=1): + if h <= int(M[dy][col]): + break + else: + visible += 1 + score.append(i) + + for i, dx in enumerate(range(col + 1, C), start=1): + if h <= int(M[row][dx]): + break + else: + visible += 1 + score.append(i) + + for i, dy in enumerate(range(row + 1, R), start=1): + if h <= int(M[dy][col]): + break + else: + visible += 1 + score.append(i) + + for i, dx in enumerate(range(col - 1, -1, -1), start=1): + if h <= int(M[row][dx]): + break + else: + visible += 1 + score.append(i) + + if visible: + p1 += 1 + p2 = max(prod(score), p2) + return p1, p2 + + +if __name__ == "__main__": + with open("./input/08.txt", "r") as f: + inp = f.read().strip() + + p1, p2 = solve(inp) + + print(p1) + print(p2) + + assert p1 == 1812 + assert p2 == 315495 diff --git a/2022-python/output/day_09.py b/2022-python/output/day_09.py new file mode 100644 index 0000000..2c1e2b7 --- /dev/null +++ b/2022-python/output/day_09.py @@ -0,0 +1,71 @@ +from output import DD, ints + + +def solve(data): + p12 = [] + for sl in [2, 10]: + seen = set([(0, 0)]) + snake = [(0, 0)] * sl + for line in data.splitlines(): + d = line[0] + for i in range(ints(line)[0]): + ns = [] + hd, *tls = snake + r, c = hd + hd = r + DD[d][0], c + DD[d][1] + ns.append(hd) + for tl in tls: + r, c = tl + match (r - hd[0], c - hd[1]): + case (2, -1): + tl = r - 1, c + 1 + case (-2, -1): + tl = r + 1, c + 1 + case (-2, 1): + tl = r + 1, c - 1 + case (2, 1): + tl = r - 1, c - 1 + case (1, 2): + tl = r - 1, c - 1 + case (-1, 2): + tl = r + 1, c - 1 + case (-1, -2): + tl = r + 1, c + 1 + case (1, -2): + tl = r - 1, c + 1 + case (0, 2): + tl = r, c - 1 + case (0, -2): + tl = r, c + 1 + case (2, 0): + tl = r - 1, c + case (-2, 0): + tl = r + 1, c + case (2, -2): + tl = r - 1, c + 1 + case (-2, -2): + tl = r + 1, c + 1 + case (-2, 2): + tl = r + 1, c - 1 + case (2, 2): + tl = r - 1, c - 1 + ns.append(tl) + hd = tl + snake = ns + seen.add(tl) + p12.append(len(seen)) + p1, p2 = p12 + return p1, p2 + + +if __name__ == "__main__": + with open("./input/09.txt", "r") as f: + inp = f.read().strip() + + p1, p2 = solve(inp) + + print(p1) + print(p2) + + assert p1 == 6212 + assert p2 == 2522 diff --git a/2022-python/output/day_10.py b/2022-python/output/day_10.py new file mode 100644 index 0000000..5ab6fce --- /dev/null +++ b/2022-python/output/day_10.py @@ -0,0 +1,49 @@ +from output import sints + + +def solve(data): + x = 1 + P = data.splitlines() + p1 = 0 + c = 0 + p = 0 + running = True + busy = False + pixels = [] + while running: + if not p < len(P) and not busy: + running = False + continue + line = P[min([p, len(P) - 1])] + if (c + 21) % 40 == 0: + p1 += (c + 1) * x + if c % 40 == 0: + pixels.append("\n") + pixels.append("■" if (x - 1) <= (c % 40) <= (x + 1) else " ") + if busy: + busy = False + x += int(sints(line)[0]) + p += 1 + c += 1 + continue + if line.startswith("noop"): + p += 1 + c += 1 + continue + busy = True + c += 1 + p2 = "".join(pixels) + return p1, p2 + + +if __name__ == "__main__": + with open("./input/10.txt", "r") as f: + inp = f.read().strip() + + p1, p2 = solve(inp) + + print(p1) + print(p2) + + assert p1 == 11820 + # assert p2 == "EPJBRKAH" diff --git a/2022-python/output/day_11.py b/2022-python/output/day_11.py new file mode 100644 index 0000000..197a2d5 --- /dev/null +++ b/2022-python/output/day_11.py @@ -0,0 +1,74 @@ +from copy import deepcopy +from collections import defaultdict +from output import ints + + +def solve(data): + M = [] + for md in data.split("\n\n"): + m = {} + starting_items, operation, divisor, if_true, if_false = md.splitlines()[1:] + m["items"] = ints(starting_items) + m["if_false"] = ints(if_false)[0] + m["if_true"] = ints(if_true)[0] + m["divisor"] = ints(divisor)[0] + m["operation_values"] = operation.split()[-3:] + M.append(m) + p12 = [] + # Part 2 resolver. + # + # Not sure what to call this, so I go for Greatest Common + # Divisor. Had to look for hints at subreddit to find it out. + # It basically fasten up the computation speed by keeping + # the numbers low. + # + # Apparently, this is common Modular arithmetic: + # https://en.wikipedia.org/wiki/Modular_arithmetic + gcd = 1 + for d in [m["divisor"] for m in M]: + gcd *= d + for rounds, wld in [(20, True), (10_000, False)]: + B = defaultdict(int) + MM = deepcopy(M) + for _ in range(rounds): + for i, m in enumerate(MM): + while m["items"]: + wl = m["items"].pop(0) + B[i] += 1 + x, y, z = m["operation_values"] + x = int(wl) if x == "old" else int(x) + z = int(wl) if z == "old" else int(z) + match y: + case "+": + wl = x + z + case "*": + wl = x * z + if wld: + wl = wl // 3 + else: + wl = wl % gcd + if wl % m["divisor"] == 0: + MM[m["if_true"]]["items"].append(wl) + else: + MM[m["if_false"]]["items"].append(wl) + a, b, *_ = sorted(B.values(), reverse=True) + p12.append(a * b) + p1, p2 = p12 + return p1, p2 + + +def _state(M): + return tuple([(tuple(m["items"])) for m in M]) + + +if __name__ == "__main__": + with open("./input/11.txt", "r") as f: + inp = f.read().strip() + + p1, p2 = solve(inp) + + print(p1) + print(p2) + + assert p1 == 50616 + assert p2 == 11309046332 diff --git a/2022-python/output/day_12.py b/2022-python/output/day_12.py new file mode 100644 index 0000000..cb6d27a --- /dev/null +++ b/2022-python/output/day_12.py @@ -0,0 +1,58 @@ +import re +from collections import deque, Counter, defaultdict +from heapq import heappop, heappush +from itertools import compress, combinations, chain, permutations + +from output import matrix, D, DDa, vvdbg, DD, ADJ, ints, mhd, mdbg, vdbg, cw, ccw, bk + + +def solve(data): + M, H, W = matrix(data) + E = S = None + p1 = p2 = H * W + SP = [] + for rr in range(H): + for cc in range(W): + if M[rr][cc] == "E": + E = (rr, cc) + if M[rr][cc] == "S": + S = (rr, cc) + SP.append((rr, cc)) + if M[rr][cc] == "a": + SP.append((rr, cc)) + M = [list(row) for row in M] + for rc, v in [(S, "a"), (E, "z")]: + r, c = rc + M[r][c] = v + for sp in SP: + Q = [(sp, ord("a"), 0)] + seen = set() + while Q: + yx, e, s = Q.pop(0) + if yx in seen: + continue + seen.add(yx) + if yx == E: + if sp == S: + p1 = s + p2 = min(p2, s) + y, x = yx + for dy, dx in D: + if 0 <= y + dy < H and 0 <= x + dx < W: + n = ord(M[y + dy][x + dx]) + if n - e <= 1: + Q.append(((y + dy, x + dx), n, s + 1)) + return p1, p2 + + +if __name__ == "__main__": + with open("./input/12.txt", "r") as f: + inp = f.read().strip() + + p1, p2 = solve(inp) + + print(p1) + print(p2) + + assert p1 == 517 + assert p2 == 512