From f0f78fa25c2b5f1464cc25224e8b16332eab7faa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Engl=C3=B6f=20Ytterstr=C3=B6m?= Date: Sun, 30 Nov 2025 19:25:39 +0100 Subject: [PATCH 01/16] Initiate Advent of Code 2025 --- 2025-python/README.md | 45 +++++++ 2025-python/aoc.py | 118 +++++++++++++++++ 2025-python/output/__init__.py | 224 +++++++++++++++++++++++++++++++++ leaderboard/Containerfile | 2 +- 4 files changed, 388 insertions(+), 1 deletion(-) create mode 100644 2025-python/README.md create mode 100644 2025-python/aoc.py create mode 100644 2025-python/output/__init__.py diff --git a/2025-python/README.md b/2025-python/README.md new file mode 100644 index 0000000..8d20c7b --- /dev/null +++ b/2025-python/README.md @@ -0,0 +1,45 @@ +# Advent of Code 2025 + +Solutions for #aoc2025 in Python 3 (3.13.4). + +Programming setup: + +- Lenovo Thinkpad T14 +- OpenSUSE Tumbleweed with labwc +- Helix editor w/ Ruff LS +- 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`). + diff --git a/2025-python/aoc.py b/2025-python/aoc.py new file mode 100644 index 0000000..d185846 --- /dev/null +++ b/2025-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 = 2025 +nostrip = [] + +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, sints, 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(24))) +print("") diff --git a/2025-python/output/__init__.py b/2025-python/output/__init__.py new file mode 100644 index 0000000..3f3f0d1 --- /dev/null +++ b/2025-python/output/__init__.py @@ -0,0 +1,224 @@ +import re +from math import inf + +# 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 grid(d, o="#"): + """Transform a string into an iterable matrix. Returns the matrix, row count and col count""" + m = [tuple(r) for r in d.split()] + return set([(r, c) for c in range(len(m[0])) for r in range(len(m)) if m[r][c] == o]) + + +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, mask=("#", "."), M=None): + """Print-debug visited positions of a matrix""" + t = inf + l = inf + b = 0 + r = 0 + for y, x in seen: + t = min(t, y) + r = max(r, x) + b = max(b, y) + l = min(l, x) + H = b - t + 1 + W = r - l + 1 + osr = t + osc = l + C, Z = mask + def _m(r, c): + return M[r][c] if M else Z + for r in range(H): + print("".join([C if (r + osr, c + osc) in seen else _m(r, c) 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/leaderboard/Containerfile b/leaderboard/Containerfile index c05837c..d72ec28 100644 --- a/leaderboard/Containerfile +++ b/leaderboard/Containerfile @@ -8,7 +8,7 @@ RUN pip install waitress==3.0.2 FROM reqs AS app RUN mkdir /app/templates -COPY *.jinja2 /app/templates +COPY templates/*.jinja2 /app/templates COPY app.py app.py ENV AOC_TOKEN= -- 2.45.3 From 56ca9d19c46bb796b458d36ed64e258d867bbd08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Engl=C3=B6f=20Ytterstr=C3=B6m?= Date: Mon, 1 Dec 2025 08:04:10 +0100 Subject: [PATCH 02/16] Solve 2025 day 1 pt 1-2 Brute force, since I was to newly awake to figure out the smart solution. --- 2025-python/output/day_01.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 2025-python/output/day_01.py diff --git a/2025-python/output/day_01.py b/2025-python/output/day_01.py new file mode 100644 index 0000000..a5db17e --- /dev/null +++ b/2025-python/output/day_01.py @@ -0,0 +1,29 @@ +def solve(data): + d = 50 + p1 = 0 + p2 = 0 + for s in data.split(): + sf0 = d == 0 + m, T = (-1, 99) if s[0] == "L" else (1, 1) + for _ in range(int(s[1:])): + d = (d + m) % 100 + if d == T: + if sf0: + sf0 = False + continue + p2 += 1 + p1 += d == 0 + return p1, 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 == 1195 + assert p2 == 6770 -- 2.45.3 From 4927b78e59f453a95517d4ecda8187e98d295400 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Engl=C3=B6f=20Ytterstr=C3=B6m?= Date: Tue, 2 Dec 2025 18:55:50 +0100 Subject: [PATCH 03/16] Solve 2025 day 2 pt 1-2 I tried to solve it without regexp at first, failed brutally. Cut the line count by 80% using a regexp instead. It was also funny to get all square roots. --- 2025-python/output/day_02.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 2025-python/output/day_02.py diff --git a/2025-python/output/day_02.py b/2025-python/output/day_02.py new file mode 100644 index 0000000..909bb95 --- /dev/null +++ b/2025-python/output/day_02.py @@ -0,0 +1,35 @@ +import re + +from output import ints + + +def solve(data): + p1 = set() + p2 = set() + R = re.compile(r"^(\w+)\1+$") + for line in data.split(","): + a, b = ints(line) + for n in range(a, b + 1): + s = str(n) + ls = len(s) + for seq in re.findall(R, s): + sqrts = [i for i in range(1, ls + 1) if ls % i == 0] + for t in sqrts: + if "".join([seq] * t) == s: + if t == 2: + p1.add(n) + p2.add(n) + return sum(p1), sum(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 == 38437576669 + assert p2 == 49046150754 -- 2.45.3 From 4a070e827b3d671be892ea9d3fd18989b28dcef8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Engl=C3=B6f=20Ytterstr=C3=B6m?= Date: Wed, 3 Dec 2025 07:30:04 +0100 Subject: [PATCH 04/16] Solve 2025 day 3 pt 1-2 For the first part, I used itertools.combinations to find the highest pairs of batteries. And as expected, that solution did not scale well for pt 2. I figured out that reducing batteries until the top most 12 (and 2) remained was the correct way to go. the _maxj(line, C) function is the hive conclusion from the solution mega thread. I really liked this brilliant use of a while loop to exlude batteries. - The first char just skip the while loop. A char emptying the battery list also does this. --- 2025-python/output/day_03.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 2025-python/output/day_03.py diff --git a/2025-python/output/day_03.py b/2025-python/output/day_03.py new file mode 100644 index 0000000..8d993b3 --- /dev/null +++ b/2025-python/output/day_03.py @@ -0,0 +1,31 @@ +def solve(data): + p1 = 0 + p2 = 0 + for line in data.splitlines(): + p1 += _maxj(line, 2) + p2 += _maxj(line, 12) + return p1, p2 + + +def _maxj(line, C): + toexcl = len(line) - C + batt = [] + for c in line: + while toexcl and batt and batt[-1] < c: + toexcl -= 1 + batt.pop() + batt.append(c) + return sum(10**x * int(y) for x, y in zip(range(C - 1, -1, -1), batt)) + + +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 == 17430 + assert p2 == 171975854269367 -- 2.45.3 From c3c2dd5759de5453e38a4fc2a728deda7155eb31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Engl=C3=B6f=20Ytterstr=C3=B6m?= Date: Thu, 4 Dec 2025 08:03:45 +0100 Subject: [PATCH 05/16] Fix aoc lib bug Lost 1h in 2025 day 4 pt 1 for this. :D Apparently, ADJ is not used that often. --- 2025-python/output/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/2025-python/output/__init__.py b/2025-python/output/__init__.py index 3f3f0d1..c1861ff 100644 --- a/2025-python/output/__init__.py +++ b/2025-python/output/__init__.py @@ -35,7 +35,7 @@ DDa = { ADJ = [ (-1, -1), (-1, 0), - (1, -1), + (-1, 1), (0, -1), (0, 1), (1, 1), -- 2.45.3 From ac6b97590c1d51b46edb09f6ae34b78c6f424fd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Engl=C3=B6f=20Ytterstr=C3=B6m?= Date: Thu, 4 Dec 2025 18:51:58 +0100 Subject: [PATCH 06/16] Solve 2025 day 4 pt 1-2 Had a bug in my aoc lib, that lost me an hour for pt 1: The ADJ dict has SW twice, and lacks NE. The bug is at least 2 years old. It was introduced in 661f18dca4 and apparently has never worked. I guess it has not been used up until now. That being said, it was straight-forward. A new helper for grid was used the first time, as well as an improved vdbg() function. --- 2025-python/output/day_04.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 2025-python/output/day_04.py diff --git a/2025-python/output/day_04.py b/2025-python/output/day_04.py new file mode 100644 index 0000000..63dc903 --- /dev/null +++ b/2025-python/output/day_04.py @@ -0,0 +1,31 @@ +from output import grid, ADJ + + +def solve(data): + p1 = 0 + p2 = set() + G = grid(data, o="@") + while True: + for r, c in G: + if sum((r + dy, c + dx) in G for dy, dx in ADJ) < 4: + p2.add((r, c)) + if p1 == 0: + p1 = len(p2) + if not G & p2: + break + G = G - p2 + p2 = len(p2) + return p1, p2 + + +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 == 1493 + assert p2 == 9194 -- 2.45.3 From 2d3caced7f3b0048194b3874d715972529aeaefa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Engl=C3=B6f=20Ytterstr=C3=B6m?= Date: Fri, 5 Dec 2025 08:27:05 +0100 Subject: [PATCH 07/16] Solve 2025 day 5 pt 1-2 Part 2 was too hard for me, since I initially worked with range(s, e). It quickly became out of hand. A funnel method was instead used. --- 2025-python/output/day_05.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 2025-python/output/day_05.py diff --git a/2025-python/output/day_05.py b/2025-python/output/day_05.py new file mode 100644 index 0000000..eda70e6 --- /dev/null +++ b/2025-python/output/day_05.py @@ -0,0 +1,31 @@ +from output import ints + + +def solve(data): + p1 = None + p2 = 0 + ranges, ids = data.split("\n\n") + ids = ints(ids) + R = [ints(line) for line in ranges.split()] + p1 = sum(any(s <= n <= e for s, e in R) for n in ids) + c = 0 + for s, e in sorted(R): + s = max(c + 1, s) + if s <= e: + p2 += e - s + 1 + c = max(e, c) + + return p1, p2 + + +if __name__ == "__main__": + with open("./input/05.txt", "r") as f: + inp = f.read().strip() + + p1, p2 = solve(inp) + + print(p1) + print(p2) + + assert p1 == 509 + assert p2 == 336790092076620 -- 2.45.3 From 68be80a4d41bb0c43ef6682c5fa894dce80914c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Engl=C3=B6f=20Ytterstr=C3=B6m?= Date: Sat, 6 Dec 2025 07:55:40 +0100 Subject: [PATCH 08/16] Solve 2025 day 6 pt 1-2 Lost 20 minutes in pt 1 not rembering that a print() of a zip consumes it and makes it not loopable: had I just used list(zip()) or removed the print, I would have had the answer in a decent time frame. Pt 2 was fun! I first experienced with ljust() and rjust(), only to realize the input was mixed. Instead, I threated the input as a grid. --- 2025-python/output/day_06.py | 40 ++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 2025-python/output/day_06.py diff --git a/2025-python/output/day_06.py b/2025-python/output/day_06.py new file mode 100644 index 0000000..8e396c7 --- /dev/null +++ b/2025-python/output/day_06.py @@ -0,0 +1,40 @@ +import re +from math import prod + + +def solve(data): + p1 = 0 + p2 = 0 + rows = data.splitlines() + J = max(len(r) for r in rows) + 1 + rows = [r.ljust(J) for r in rows] + ops = rows.pop() + s = 0 + for ows in re.findall(r"(\S\s+)", ops): + o, *ws = ows + e = s + len(ws) + col = [ns for ns in [r[s:e] for r in rows]] + s += len(ows) + col1 = [int(r) for r in col] + col2 = [int("".join([nsc for nsc in ns if nsc.isdigit()])) for ns in zip(*col)] + match o: + case "*": + p1 += prod(col1) + p2 += prod(col2) + case "+": + p1 += sum(col1) + p2 += sum(col2) + 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 == 5524274308182 + assert p2 == 8843673199391 -- 2.45.3 From 8683885438526794e6dfcb966f54544beabb3bc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Engl=C3=B6f=20Ytterstr=C3=B6m?= Date: Sun, 7 Dec 2025 14:57:28 +0100 Subject: [PATCH 09/16] Solve 2025 day 7 pt 1-2 For part 1, BFS is used since there is no need to visit each splitter more than once to count visited splitters. For part 2, the code initially removed the visited check. This failed miserably, losing momentum aound Y=54-56. A recursive function with memoization solves it much faster. --- 2025-python/output/day_07.py | 50 ++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 2025-python/output/day_07.py diff --git a/2025-python/output/day_07.py b/2025-python/output/day_07.py new file mode 100644 index 0000000..22b6c25 --- /dev/null +++ b/2025-python/output/day_07.py @@ -0,0 +1,50 @@ +import functools +from output import grid + + +def solve(data): + G = grid(data, o="^") + p1 = set() + p2 = 0 + H = len(data.split()) + W = len(data.split()[0]) + S = (0, data.split()[0].index("S")) + Q = [S] + while Q: + y, x = Q.pop(0) + if y == H: + continue + if (y, x) in p1: + continue + if (y, x) in G: + Q.append((y, x - 1)) + Q.append((y, x + 1)) + p1.add((y, x)) + else: + Q.append((y + 1, x)) + p1 = len(p1) + + @functools.cache + def _timelines(p): + y, x = p + if not 0 <= y < H or not 0 <= x < W: + return 1 + if p in G: + return _timelines((y, x - 1)) + _timelines((y, x + 1)) + return _timelines((y + 1, x)) + + p2 = _timelines(S) + 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 == 1539 + assert p2 == 6479180385864 -- 2.45.3 From 34fb92403e843bcd59793dc46bfeda7d75893425 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Engl=C3=B6f=20Ytterstr=C3=B6m?= Date: Mon, 8 Dec 2025 07:56:36 +0100 Subject: [PATCH 10/16] Solve 2025 day 8 pt 1-2 For the First time this season, pt 2 was not an epic HOLY SHIIIT!!! facemelter. I stared at my code in disbelief during pt 1 for several minutes before I increased the test point from from 40 to 1000 and got the right answer. --- 2025-python/output/day_08.py | 58 ++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 2025-python/output/day_08.py diff --git a/2025-python/output/day_08.py b/2025-python/output/day_08.py new file mode 100644 index 0000000..a1aa697 --- /dev/null +++ b/2025-python/output/day_08.py @@ -0,0 +1,58 @@ +from math import sqrt +from itertools import combinations + +from output import ints + + +def solve(data): + Di = sorted( + map(_euclidan_distance, combinations(data.splitlines(), r=2)), + key=lambda r: r[1], + ) + C = [] + S = len(data.splitlines()) + IP = 1000 + for i, pq in enumerate(Di, start=1): + pq, _ = pq + c = set(pq) + p, q = pq + rmq = [] + nothing = False + for j in C: + if p in j and q in j: + nothing = True + continue + if p in j or q in j: + c = c | j + rmq.append(j) + C = [e for e in C if e not in rmq] + if c == pq and nothing: + continue + if len(c) == S: + p2 = ints(p)[0] * ints(q)[0] + break + C = [c] + C + if i == IP: + a, b, c = sorted(list(map(len, C)), reverse=True)[:3] + p1 = a * b * c + return p1, p2 + + +def _euclidan_distance(pq): + p, q = pq + p1, p2, p3 = ints(p) + q1, q2, q3 = ints(q) + return (p, q), sqrt((p1 - q1) ** 2 + (p2 - q2) ** 2 + (p3 - q3) ** 2) + + +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 == 84968 + assert p2 == 8663467782 -- 2.45.3 From ae676bf112cdf5e4185d7e34fdf53bec85b52806 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Engl=C3=B6f=20Ytterstr=C3=B6m?= Date: Tue, 9 Dec 2025 14:49:19 +0100 Subject: [PATCH 11/16] Solve 2025 day 9 pt 1 Pt 2 is almost there. Need a better flood fill algorithm. --- 2025-python/output/day_09.py | 95 ++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 2025-python/output/day_09.py diff --git a/2025-python/output/day_09.py b/2025-python/output/day_09.py new file mode 100644 index 0000000..844d437 --- /dev/null +++ b/2025-python/output/day_09.py @@ -0,0 +1,95 @@ +from itertools import combinations + +from output import ints + + +def solve(data): + p1 = 0 + p2 = 0 + A = [tuple(ints(pos)) for pos in data.splitlines()] + + S = A[0] + V = set() + B = set() + + while True: + V.add(S) + y, x = S + he = [(r, c) for r, c in A if r == y and (r, c) not in V] + ve = [(r, c) for r, c in A if c == x and (r, c) not in V] + if not he and not ve: + E = A[0] + elif he and ve: + E = min(min(he), min(ve)) + elif he and not ve: + E = min(he) + else: + E = min(ve) + y2, x2 = E + for r in range(min(y, y2), max(y, y2) + 1): + for c in range(min(x, x2), max(x, x2) + 1): + B.add((r, c)) + if E == A[0]: + break + S = E + # find out a better start for Flood fill by visualizing + Y = 1841 + X = 53478 + # or, find a way to floodfill without BFS + # end + y, x = Y, X + Q = [(y, x)] + V = V | B + while Q: + yx = Q.pop() + if yx in V: + continue + V.add(yx) + y, x = yx + for dy, dx in D: + Q.append((dy + y, dx + x)) + for a, b in combinations(A, r=2): + y1, x1 = a + y2, x2 = b + x = abs(x1 - x2) + 1 + y = abs(y1 - y2) + 1 + p1 = max(p1, x * y) + if ( + len( + set( + [ + (min(y1, y2), min(x1, x2)), + (min(y1, y2), max(x1, x2)), + (max(y1, y2), min(x1, x2)), + (max(y1, y2), max(x1, x2)), + ] + ) + - V + ) + == 0 + ): + p2 = max(p2, max(p2, x * y)) + return p1, p2 + + +if __name__ == "__main__": + inp = """ +7,1 +11,1 +11,7 +9,7 +9,5 +2,5 +2,3 +7,3 """.strip() + + with open("./input/09.txt", "r") as f: + inp = f.read().strip() + + p1, p2 = solve(inp) + + print(p1) + print(p2) + + assert p1 == 4749838800 + assert p2 == 1624057680 -- 2.45.3 From b8409db06cbf8bc13b0cac539c3590cbd16983e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Engl=C3=B6f=20Ytterstr=C3=B6m?= Date: Tue, 9 Dec 2025 19:27:43 +0100 Subject: [PATCH 12/16] Solve 2025 day 9 pt 2 Flood fill was too expensive, so I tried solving it with ray casting. I believe it is the 2nd or 3rd time during 11 runs where it has been requested. wip wip --- 2025-python/output/__init__.py | 43 ++++++++++++++++--- 2025-python/output/day_09.py | 77 +++++++++++++++------------------- 2 files changed, 71 insertions(+), 49 deletions(-) diff --git a/2025-python/output/__init__.py b/2025-python/output/__init__.py index c1861ff..8c26c52 100644 --- a/2025-python/output/__init__.py +++ b/2025-python/output/__init__.py @@ -1,5 +1,6 @@ import re from math import inf +from PIL import Image # Directions/Adjacents for 2D matrices, in the order UP, RIGHT, DOWN, LEFT D = [ @@ -90,15 +91,47 @@ def vdbg(seen, mask=("#", "."), M=None): r = max(r, x) b = max(b, y) l = min(l, x) - H = b - t + 1 - W = r - l + 1 - osr = t - osc = l + H = b + 1 + W = r + 1 + osr = 0 #t + osc = 0 #l C, Z = mask def _m(r, c): return M[r][c] if M else Z + O = [] for r in range(H): - print("".join([C if (r + osr, c + osc) in seen else _m(r, c) for c in range(W)])) + O.append("".join([C if (r + osr, c + osc) in seen else _m(r, c) for c in range(W)])) + print("\n".join(O)) + + +def svg(seen): + """Print-debug visited positions of a matrix""" + t = inf + l = inf + b = 0 + r = 0 + rects = [] + for y, x in seen: + t = min(t, y) + r = max(r, x) + b = max(b, y) + l = min(l, x) + H = b - t + 1 + W = r - l + 1 + print(t, r, b, l) + im = Image.new(mode="RGB", size=(W,H), color=(255,255,255)) + for y, x in seen: + im.putpixel((x-l, y-t), (0, 0, 0, 255)) + im.save("aoc.png") + + for y, x in seen: + rects.append(f"") + with open("svg.svg", "w") as f: + f.write(f""" + + {"".join(rects)} + + """.strip()) def vvdbg(seen, h, w): diff --git a/2025-python/output/day_09.py b/2025-python/output/day_09.py index 844d437..9bc5f03 100644 --- a/2025-python/output/day_09.py +++ b/2025-python/output/day_09.py @@ -1,3 +1,4 @@ +from math import inf from itertools import combinations from output import ints @@ -7,13 +8,21 @@ def solve(data): p1 = 0 p2 = 0 A = [tuple(ints(pos)) for pos in data.splitlines()] - + T = inf + R = 0 + B = 0 + L = inf + for r, c in A: + T = min(r, T) + R = max(c, R) + B = max(r, B) + L = min(c, L) S = A[0] V = set() - B = set() - + W = set() while True: V.add(S) + W.add(S) y, x = S he = [(r, c) for r, c in A if r == y and (r, c) not in V] ve = [(r, c) for r, c in A if c == x and (r, c) not in V] @@ -28,61 +37,41 @@ def solve(data): y2, x2 = E for r in range(min(y, y2), max(y, y2) + 1): for c in range(min(x, x2), max(x, x2) + 1): - B.add((r, c)) + W.add((r, c)) if E == A[0]: break S = E - # find out a better start for Flood fill by visualizing - Y = 1841 - X = 53478 - # or, find a way to floodfill without BFS - # end - y, x = Y, X - Q = [(y, x)] - V = V | B - while Q: - yx = Q.pop() - if yx in V: - continue - V.add(yx) - y, x = yx - for dy, dx in D: - Q.append((dy + y, dx + x)) + V = V | W for a, b in combinations(A, r=2): y1, x1 = a y2, x2 = b x = abs(x1 - x2) + 1 y = abs(y1 - y2) + 1 p1 = max(p1, x * y) - if ( - len( - set( - [ - (min(y1, y2), min(x1, x2)), - (min(y1, y2), max(x1, x2)), - (max(y1, y2), min(x1, x2)), - (max(y1, y2), max(x1, x2)), - ] - ) - - V - ) - == 0 - ): + if _within(W, a, b, T, R, B, L): p2 = max(p2, max(p2, x * y)) return p1, p2 -if __name__ == "__main__": - inp = """ -7,1 -11,1 -11,7 -9,7 -9,5 -2,5 -2,3 -7,3 """.strip() +def _within(W, a, b, T, R, B, L): + y1, x1 = a + y2, x2 = b + for r in range(min(y1, y2) + 1, max(y1, y2)): + for c in range(min(x1, x2) + 1, max(x1, x2)): + if (r, c) in W: + continue + for s, e in [(T, r), (r, B)]: + z = sum((nr, c) in W for nr in range(s, e)) + if z and z % 2 == 0: + return False + for s, e in [(L, c), (c, R)]: + z = sum((r, nc) in W for nc in range(s, e)) + if z and z % 2 == 0: + return False + return True + +if __name__ == "__main__": with open("./input/09.txt", "r") as f: inp = f.read().strip() -- 2.45.3 From 0723a8223f466c82398b30306e82ab63d56e1a39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Engl=C3=B6f=20Ytterstr=C3=B6m?= Date: Thu, 11 Dec 2025 20:45:13 +0100 Subject: [PATCH 13/16] day 10 --- 2025-python/output/day_10.py | 58 ++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 2025-python/output/day_10.py diff --git a/2025-python/output/day_10.py b/2025-python/output/day_10.py new file mode 100644 index 0000000..c33f965 --- /dev/null +++ b/2025-python/output/day_10.py @@ -0,0 +1,58 @@ +import re +from pprint import pprint +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, sints, mhd, mdbg, vdbg, cw, ccw, bk + + +def solve(data): + r = r"^\[(.+)\] (.+) \{(.+)\}$" + p1 = 0 + for line in data.splitlines(): + il, b, _j = re.findall(r, line)[0] + B = [set(ints(s)) for s in b.split()] + E = set([i for i, s in enumerate(il) if s == "#"]) + Q = [(b, set(), 0) for b in B] + while Q: + b, lit, p = Q.pop(0) + if lit == E: + p1 += p + break + for nb in B: + if nb == b: + continue + Q.append((nb, lit ^ b, p + 1)) + p2 = None + return p1, p2 + + +if __name__ == "__main__": + import os + + # use dummy data + inp = """ +[.##.] (3) (1,3) (2) (2,3) (0,2) (0,1) {3,5,4,7} +[...#.] (0,2,3,4) (2,3) (0,4) (0,1,2) (1,2,3,4) {7,5,12,7,2} +[.###.#] (0,1,2,3,4) (0,3,4) (0,1,2,4,5) (1,2) {10,11,11,5,10,5} """.strip() + + # uncomment to instead use stdin + # import sys; inp = sys.stdin.read().strip() + + # uncomment to use AoC provided puzzle input + with open("./input/10.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 -- 2.45.3 From 044df2499049b4494ca892e3dd619cff1bfc71a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Engl=C3=B6f=20Ytterstr=C3=B6m?= Date: Thu, 11 Dec 2025 21:42:15 +0100 Subject: [PATCH 14/16] Solve 2025 day 11 pt 1-2 For p1 a BFS algorithm is fast enough. For pt 2, for the third day in a row, it did not work. I caved in and used recursion and memoisation for DFS instead, instantly fast as lightning. I finished by making the pt 2 code compatible with pt 1. --- 2025-python/output/day_11.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 2025-python/output/day_11.py diff --git a/2025-python/output/day_11.py b/2025-python/output/day_11.py new file mode 100644 index 0000000..61b7a0f --- /dev/null +++ b/2025-python/output/day_11.py @@ -0,0 +1,33 @@ +import functools +from collections import defaultdict + + +def solve(data): + G = defaultdict(set) + for line in data.splitlines(): + f, *t = line.split() + G[f[:-1]] = set(t) + + @functools.cache + def _traverse(k, fasttrack=True, fft=False, dac=False): + if k == "out": + return fasttrack or (fft and dac) + return sum( + _traverse(nk, fasttrack, fft or k == "fft", dac or k == "dac") + for nk in G[k] + ) + + return _traverse("you"), _traverse("svr", fasttrack=False) + + +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 == 649 + assert p2 == 458948453421420 -- 2.45.3 From 01bfcc1351f5a99d667fd9a929208f79f665c845 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Engl=C3=B6f=20Ytterstr=C3=B6m?= Date: Thu, 11 Dec 2025 23:36:38 +0100 Subject: [PATCH 15/16] . --- 2025-python/output/day_09.py | 55 +++++++++++++++++++++++------------- 2025-python/output/day_10.py | 48 ++++++++++++++++++++++--------- 2025-python/output/day_11.py | 6 ++-- 3 files changed, 72 insertions(+), 37 deletions(-) diff --git a/2025-python/output/day_09.py b/2025-python/output/day_09.py index 9bc5f03..17434b1 100644 --- a/2025-python/output/day_09.py +++ b/2025-python/output/day_09.py @@ -1,6 +1,5 @@ from math import inf from itertools import combinations - from output import ints @@ -42,43 +41,59 @@ def solve(data): break S = E V = V | W + + def _within(a, b): + y1, x1 = a + y2, x2 = b + T = min(y1, y2) + R = max(x1, x2) + B = max(y1, y2) + L = min(x1, x2) + return all([ + sum((r, L + 1) in W for r in range(T + 1, B)) % 2 == 0, + sum((r, R - 1) in W for r in range(T + 1, B)) % 2 == 0, + sum((T + 1, c) in W for c in range(L + 1, R)) % 2 == 0, + sum((B - 1, c) in W for c in range(L + 1, R)) % 2 == 0, + ]) + + S = len(list(combinations(A, r=2))) + i = 0 for a, b in combinations(A, r=2): + i += 1 + if i % 100 == 0: + print(f"{str(i):>6} / {S}") y1, x1 = a y2, x2 = b x = abs(x1 - x2) + 1 y = abs(y1 - y2) + 1 p1 = max(p1, x * y) - if _within(W, a, b, T, R, B, L): + if _within(a, b): p2 = max(p2, max(p2, x * y)) return p1, p2 -def _within(W, a, b, T, R, B, L): - y1, x1 = a - y2, x2 = b - for r in range(min(y1, y2) + 1, max(y1, y2)): - for c in range(min(x1, x2) + 1, max(x1, x2)): - if (r, c) in W: - continue - for s, e in [(T, r), (r, B)]: - z = sum((nr, c) in W for nr in range(s, e)) - if z and z % 2 == 0: - return False - for s, e in [(L, c), (c, R)]: - z = sum((r, nc) in W for nc in range(s, e)) - if z and z % 2 == 0: - return False - return True if __name__ == "__main__": with open("./input/09.txt", "r") as f: inp = f.read().strip() + in_p = """ + 7,1 +11,1 +11,7 +9,7 +9,5 +2,5 +2,3 +7,3 """.strip() + p1, p2 = solve(inp) print(p1) print(p2) - assert p1 == 4749838800 - assert p2 == 1624057680 + assert p1 == 50 + assert p2 == 24 + # assert p1 == 4749838800 + # assert p2 == 1624057680 diff --git a/2025-python/output/day_10.py b/2025-python/output/day_10.py index c33f965..2aee967 100644 --- a/2025-python/output/day_10.py +++ b/2025-python/output/day_10.py @@ -1,4 +1,6 @@ import re +import functools +from math import inf from pprint import pprint from collections import deque, Counter, defaultdict from heapq import heappop, heappush @@ -10,20 +12,38 @@ from output import matrix, D, DD, ADJ, ints, sints, mhd, mdbg, vdbg, cw, ccw, bk def solve(data): r = r"^\[(.+)\] (.+) \{(.+)\}$" p1 = 0 - for line in data.splitlines(): - il, b, _j = re.findall(r, line)[0] + + for j, line in enumerate(data.splitlines(), start=1): + il, b, j = re.findall(r, line)[0] B = [set(ints(s)) for s in b.split()] E = set([i for i, s in enumerate(il) if s == "#"]) - Q = [(b, set(), 0) for b in B] - while Q: - b, lit, p = Q.pop(0) - if lit == E: - p1 += p - break - for nb in B: - if nb == b: - continue - Q.append((nb, lit ^ b, p + 1)) + J = ints(j) + + p = 0 + L = set() + while L != E: + p += 1 + for bp in combinations(B, p): + L = set() + for b in bp: + L = L ^ b + if L == E: + break + if L == E: + break + # p = 0 + # L = [0] * len(J) + # while L != J: + # p += 1 + # for bp in combinations(B, p): + # L = set() + # for b in bp: + # L = L ^ b + # if L == E: + # break + # if L == E: + # break + p1 += p p2 = None return p1, p2 @@ -49,8 +69,8 @@ if __name__ == "__main__": print(p1) os.system(f"echo {p1} | wl-copy") - # print(p2) - # os.system(f"echo {p2} | 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 diff --git a/2025-python/output/day_11.py b/2025-python/output/day_11.py index 61b7a0f..7f6c25f 100644 --- a/2025-python/output/day_11.py +++ b/2025-python/output/day_11.py @@ -3,10 +3,10 @@ from collections import defaultdict def solve(data): - G = defaultdict(set) + G = defaultdict(list) for line in data.splitlines(): f, *t = line.split() - G[f[:-1]] = set(t) + G[f[:-1]] = t @functools.cache def _traverse(k, fasttrack=True, fft=False, dac=False): @@ -15,7 +15,7 @@ def solve(data): return sum( _traverse(nk, fasttrack, fft or k == "fft", dac or k == "dac") for nk in G[k] - ) + ) return _traverse("you"), _traverse("svr", fasttrack=False) -- 2.45.3 From 11137463c701a14d10824a2917bdc25c7392613a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Engl=C3=B6f=20Ytterstr=C3=B6m?= Date: Sun, 14 Dec 2025 22:41:23 +0100 Subject: [PATCH 16/16] Solve 2025 day 12 pt 1-2 Got this one ruined due to a post not masking spoilers: https://www.reddit.com/r/adventofcode/comments/1pkxibs/2025_day_25_part_1_still_pretty_clueless_why_its/ Basically, ignore the test case and just wing it by asuming the 3x3 can be placed without overlaps. Id the number of shapes * 3x3 is bigger than the available canvas, it won't fit. A naive first check to do before making premature optimizations. I would have gone the zip() hell route to prove it actually works, but after grasping at the puzzle input i realized it would tka a looong time to calculate. --- 2025-python/output/day_12.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 2025-python/output/day_12.py diff --git a/2025-python/output/day_12.py b/2025-python/output/day_12.py new file mode 100644 index 0000000..c80a34a --- /dev/null +++ b/2025-python/output/day_12.py @@ -0,0 +1,26 @@ +from output import ints + + +def solve(data): + p1 = 0 + data = data.split("\n\n") + Q = data.pop().splitlines() + for q in Q: + W, H, *C = ints(q) + if W * H >= sum(C) * 9: + p1 += 1 + p2 = "God jul!" + 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 == 440 + assert p2 == "God jul!" -- 2.45.3