2025 #5
16 changed files with 962 additions and 1 deletions
45
2025-python/README.md
Normal file
45
2025-python/README.md
Normal file
|
|
@ -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/<day_no>.txt` and `output/day_<day_no>.py`):
|
||||
|
||||
python aoc.py <day_no> new
|
||||
|
||||
Manually copy the puzzle input from https://adventofcode.com and paste it in `input/<day_no>.txt`
|
||||
to start coding.
|
||||
|
||||
wl-paste > input/<day_no>.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`).
|
||||
|
||||
118
2025-python/aoc.py
Normal file
118
2025-python/aoc.py
Normal file
|
|
@ -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("")
|
||||
257
2025-python/output/__init__.py
Normal file
257
2025-python/output/__init__.py
Normal file
|
|
@ -0,0 +1,257 @@
|
|||
import re
|
||||
from math import inf
|
||||
from PIL import Image
|
||||
|
||||
# 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 + 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):
|
||||
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"<rect x='{x-l}' y='{y-t}' width='1' fill='black' stroke='black' stroke-width='10' height='1' />")
|
||||
with open("svg.svg", "w") as f:
|
||||
f.write(f"""
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {W} {H}" width="{W}" height="{H}">
|
||||
{"".join(rects)}
|
||||
</svg>
|
||||
""".strip())
|
||||
|
||||
|
||||
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)
|
||||
29
2025-python/output/day_01.py
Normal file
29
2025-python/output/day_01.py
Normal file
|
|
@ -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
|
||||
35
2025-python/output/day_02.py
Normal file
35
2025-python/output/day_02.py
Normal file
|
|
@ -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
|
||||
31
2025-python/output/day_03.py
Normal file
31
2025-python/output/day_03.py
Normal file
|
|
@ -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
|
||||
31
2025-python/output/day_04.py
Normal file
31
2025-python/output/day_04.py
Normal file
|
|
@ -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
|
||||
31
2025-python/output/day_05.py
Normal file
31
2025-python/output/day_05.py
Normal file
|
|
@ -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
|
||||
40
2025-python/output/day_06.py
Normal file
40
2025-python/output/day_06.py
Normal file
|
|
@ -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
|
||||
50
2025-python/output/day_07.py
Normal file
50
2025-python/output/day_07.py
Normal file
|
|
@ -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
|
||||
58
2025-python/output/day_08.py
Normal file
58
2025-python/output/day_08.py
Normal file
|
|
@ -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
|
||||
99
2025-python/output/day_09.py
Normal file
99
2025-python/output/day_09.py
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
from math import inf
|
||||
from itertools import combinations
|
||||
from output import ints
|
||||
|
||||
|
||||
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()
|
||||
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]
|
||||
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):
|
||||
W.add((r, c))
|
||||
if E == A[0]:
|
||||
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(a, b):
|
||||
p2 = max(p2, max(p2, x * y))
|
||||
return p1, p2
|
||||
|
||||
|
||||
|
||||
|
||||
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 == 50
|
||||
assert p2 == 24
|
||||
# assert p1 == 4749838800
|
||||
# assert p2 == 1624057680
|
||||
78
2025-python/output/day_10.py
Normal file
78
2025-python/output/day_10.py
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import re
|
||||
import functools
|
||||
from math import inf
|
||||
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 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 == "#"])
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
33
2025-python/output/day_11.py
Normal file
33
2025-python/output/day_11.py
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import functools
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
def solve(data):
|
||||
G = defaultdict(list)
|
||||
for line in data.splitlines():
|
||||
f, *t = line.split()
|
||||
G[f[:-1]] = 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
|
||||
26
2025-python/output/day_12.py
Normal file
26
2025-python/output/day_12.py
Normal file
|
|
@ -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!"
|
||||
|
|
@ -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=
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue