Compare commits

..

9 Commits

15 changed files with 511 additions and 342 deletions

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
Pipfile
__pycache__ __pycache__
*.txt *.txt

View File

@@ -1,3 +1,11 @@
My solutions to the Advent of Code 2023 programming challenges.
Thanks to Eric Wastl for creating this enjoyable event.
- Requires `lib.py` from [aocpy](https://git.felixm.de/felixm/aocpy) repository.
- Requires `sympy` for day 24.
- Requires `matplotlib` and `networkx` for hands-on day 25.
# Times # Times
- Day 1: 40:00 (I don't know what I am doing.) - Day 1: 40:00 (I don't know what I am doing.)
@@ -31,8 +39,15 @@
the input conjunction gate pretty early, but then messed up the the input conjunction gate pretty early, but then messed up the
implementation and thought it wasn't gonna work. Spent a half day thinking up implementation and thought it wasn't gonna work. Spent a half day thinking up
something else before returning to the idea and it worked flawlessly. something else before returning to the idea and it worked flawlessly.
- Day 21: - Day 21: Part 1 was straightforward, but part 2 maybe the hardest problem this
year.
- Day 22: Not too hard, but definitely way too slow for leaderboard. - Day 22: Not too hard, but definitely way too slow for leaderboard.
- Day 23: - Day 23: I found this fun because it required some creativity for part 2. Slow
- Day 24: af, of course.
- Day 25: - Day 24: Solve problem with sympy. I first used numpy to solve part 1 and it
was much faster than using sympy, but I lost that solution when switching to
sympy. Takes about three minutes to run for part 1 and then part 2 is under a
second.
- Day 25: I cheeky solved this by plotting the graph and manually removing the
nodes. I should probably try to write an algorith that does that, but meh.
Manually plotting requires matplotlib and networkx packages.

2
d12.py
View File

@@ -39,7 +39,7 @@ def solve(lines: list[str], repeat=1):
all = [] all = []
for (_, line) in enumerate(lines): for (_, line) in enumerate(lines):
springs, numbers = line.split() springs, numbers = line.split()
numbers = tuple(lib.str_to_int_list(numbers)) numbers = tuple(lib.str_to_ints(numbers))
ns = "?".join([springs for _ in range(repeat)]) ns = "?".join([springs for _ in range(repeat)])
all.append((tuple(ns), numbers * repeat)) all.append((tuple(ns), numbers * repeat))

3
d19.py
View File

@@ -140,10 +140,9 @@ def main():
# 25:00 # 25:00
print("Example 2:", solve(Input(EXAMPLE), True)) print("Example 2:", solve(Input(EXAMPLE), True))
print("Correct 2:", 167409079868000) assert solve(Input(EXAMPLE), True) == 167409079868000
print("Solution 2:", solve(Input(DAY_INPUT), True)) print("Solution 2:", solve(Input(DAY_INPUT), True))
# 120:00 # 120:00
return
if __name__ == "__main__": if __name__ == "__main__":
main() main()

1
d20.py
View File

@@ -103,6 +103,7 @@ def main():
print("Example 2:", solve(Input(EXAMPLE2))) print("Example 2:", solve(Input(EXAMPLE2)))
print("Solution 1:", solve(Input(DAY_INPUT))) print("Solution 1:", solve(Input(DAY_INPUT)))
print("Solution 2:", solve(Input(DAY_INPUT), True)) print("Solution 2:", solve(Input(DAY_INPUT), True))
assert solve(Input(DAY_INPUT), True) == 244178746156661
if __name__ == "__main__": if __name__ == "__main__":
main() main()

192
d21.py
View File

@@ -1,7 +1,7 @@
from lib import * from lib import *
import os
EXAMPLE = """ EXAMPLE = """...........
...........
.....###.#. .....###.#.
.###.##..#. .###.##..#.
..#.#...#.. ..#.#...#..
@@ -14,13 +14,11 @@ EXAMPLE = """
........... ...........
""" """
def solve(i: Input, second=False): def solve(input: Input):
res = 0 g = input.grid2()
g = i.grid2()
s = g.find('S')[0] s = g.find('S')[0]
g[s] = 'O' g[s] = 'O'
# steps = 64 steps = 64
steps = 26501365
seen = set() seen = set()
for i in range(steps): for i in range(steps):
os = tuple(g.find('O')) os = tuple(g.find('O'))
@@ -38,18 +36,178 @@ def solve(i: Input, second=False):
g[nb] = 'O' g[nb] = 'O'
return len(g.find('O')) return len(g.find('O'))
def plot(xs, poss):
os.system("clear")
rcoords = [x[0] for x in xs]
ccoords = [x[1] for x in xs]
rmin = min(rcoords)
rmax = max(rcoords)
cmin = min(ccoords)
cmax = max(ccoords)
for r in range(rmin, rmax + 1):
s = ""
for c in range(cmin, cmax + 1):
if (r, c) in xs:
s += "#"
elif (r, c) in poss:
s += "O"
else:
s += " "
print(s)
def move(xs, roff, coff):
rcoords = [x[0] for x in xs]
ccoords = [x[1] for x in xs]
rd = max(rcoords) - min(rcoords) + 3
cd = max(ccoords) - min(ccoords) + 3
newxs = [(x[0] + roff * rd, x[1] + coff * cd) for x in xs]
return set(newxs)
def iter(poss, stones):
nposs = set()
for r, c in poss:
for ro, co in [(-1, 0), (0, 1), (1, 0), (0, -1)]:
nr, nc = r + ro, c + co
if not (nr, nc) in stones:
nposs.add((nr, nc))
return nposs
def get_bounds(size, ro, co):
rmin = size * ro
rmax = size + size * ro
cmin = size * co
cmax = size + size * co
return rmin, rmax, cmin, cmax
def count(poss, size, ro, co):
rmin, rmax, cmin, cmax = get_bounds(size, ro, co)
res = 0
for (r, c) in poss:
if (rmin <= r < rmax) and (cmin <= c < cmax):
res += 1
return res
def solve2(ip: Input):
base_stones = set()
poss = set()
size = len(ip.lines())
assert size == len(ip.lines()[0])
for r, row in enumerate(ip.lines()):
for c, col in enumerate(row):
if col == "#":
base_stones.add((r, c))
if col == "S":
poss.add((r, c))
stones = base_stones.copy()
off = 19 // 2
for ro in range(-off, off + 1):
for co in range(-off, off + 1):
stones |= move(base_stones, ro, co)
hists = {}
for ro in range(-off, off + 1):
for co in range(-off, off + 1):
hists[(ro, co)] = []
#for step in range(590):
# if step % 1 == 0:
# sanity = 0
# os.system("clear")
# for ro in range(-off, off + 1):
# s = ""
# for co in range(-off, off + 1):
# v = count(poss, size, ro, co)
# sanity += v
# if v > 0:
# hists[(ro, co)].append(v)
# s += f"{v:6}"
# else:
# s += 6 * " "
# print(s)
# # input(f"{step=} {step//size=} {len(poss)} ({sanity}) cont...")
# print(f"{step=} {step//size=} {len(poss)} ({sanity}) cont...")
# poss = iter(poss, stones)
# 66, 197, 328 459 # cycle starts
# 196, 327, 458, 589 # targets
def calc(len, xs):
if len % 2 == 0:
return len // 2 * sum(xs)
else:
return len // 2 * sum(xs) + xs[0]
target = 196
target = 327
target = 458
target = 589
target = 26501365
# for target in [196, 327, 458, 589]:
print()
print(target)
cycle = 131
c = target // cycle
d = (target // cycle) * 2 + 1 - 2
print(f"{c=} {d=}")
res = 0
res += 5698 + 5703 + 5709 + 5704 # corners
res += c * 964 + c * 984 + c * 968 + c * 978 # outer
res += (c - 1) * 6637 + (c - 1) * 6624 + (c - 1) * 6643 + (c - 1) * 6619 # inner
for i in range(d, 0, -2):
res += calc(i, [7623, 7558])
for i in range(d - 2, 0, -2):
res += calc(i, [7623, 7558])
print(res)
return res
# def get_till(xs, ts):
# ts = ts[:]
# r = []
# for x in xs:
# r.append(x)
# if x in ts:
# ts.remove(x)
# if ts == []:
# break
# return r
osz_values = hists[(0, 4)][-2:]
# se = get_till(hists[0, 5], osz_values)
# sn = get_till(hists[-5, 0], osz_values)
# ss = get_till(hists[5, 0], osz_values)
# sw = get_till(hists[0, -5], osz_values)
# print(se)
# print(sn)
# print(sw)
# print(ss)
# sne = get_till(hists[-5, 5], osz_values)
# sse = get_till(hists[5, 5], osz_values)
# ssw = get_till(hists[5, -5], osz_values)
# snw = get_till(hists[-5, -5], osz_values)
# print(sne)
# print(sse)
# print(ssw)
# print(snw)
# for i in range(3, 10):
# print(hists[(0, i)][:5])
# print(hists[(0, -i)][:5])
def main(): def main():
DAY_INPUT = "i21.txt" DAY_INPUT = "i21.txt"
# print("Example 1:", solve(Input(EXAMPLE)))
print("Example 1:", solve(Input(EXAMPLE))) # print("Solution 1:", solve(Input(DAY_INPUT)))
print("Solution 1:", solve(Input(DAY_INPUT))) # print("Example 2:", solve2(Input(EXAMPLE)))
return print("Solution 2:", solve2(Input(DAY_INPUT)))
print("Example 2:", solve(Input(EXAMPLE), True))
return
print("Solution 2:", solve(Input(DAY_INPUT), True))
return
if __name__ == "__main__": if __name__ == "__main__":
main() main()

146
d23.py Normal file
View File

@@ -0,0 +1,146 @@
from lib import *
from collections import deque
EXAMPLE = """#.#####################
#.......#########...###
#######.#########.#.###
###.....#.>.>.###.#.###
###v#####.#v#.###.#.###
###.>...#.#.#.....#...#
###v###.#.#.#########.#
###...#.#.#.......#...#
#####.#.#.#######.#.###
#.....#.#.#.......#...#
#.#####.#.#.#########v#
#.#...#...#...###...>.#
#.#.#v#######v###.###v#
#...#.>.#...>.>.#.###.#
#####v#.#.###v#.#.###.#
#.....#...#...#.#.#...#
#.#########.###.#.#.###
#...###...#...#...#.###
###.###.#.###v#####v###
#...#...#.#.>.>.#.>.###
#.###.###.#.###.#.#v###
#.....###...###...#...#
#####################.#
"""
SLOPES = {
"^": (-1, 0),
">": (0, 1),
"v": (1, 0),
"<": (0, -1),
}
def first(input):
g = input.grid2()
start = (0, 1)
end = (g.n_rows - 1, g.n_cols - 2)
longest = 0
paths = [(set([start]), start)]
while True:
new_paths = []
for p in paths:
hist, pos = p
for d in g.COORDS_ORTH:
nb = add2(pos, d)
if nb[0] < 0 or nb[0] >= g.n_rows or nb[1] < 0 or nb[1] >= g.n_cols:
continue
c = g[nb]
if c in SLOPES.keys() and d != SLOPES[c]:
continue
if c == "#" or nb in hist:
continue
if nb == end:
l = len(hist)
if l > longest:
longest = l
continue
nhist = hist.copy()
nhist.add(nb)
new_paths.append((nhist, nb))
paths = new_paths
if len(paths) == 0:
break
return longest
def solve(input: Input, second=False):
if not second:
return first(input)
g = input.grid2()
start = (0, 1)
end = (g.n_rows - 1, g.n_cols - 2)
seen = set()
q = deque([[start, (1, 1)]])
# The intuition is that we can brute force much quicker if we have a pure
# graph instead of following the maze along the whole time. So, we create
# a graph from the maze and then brute force on the maze.
sg = {start: set()} # {node: {(node, dist), ...}}
while q:
trail = q.popleft()
pos = trail[-1]
while True:
nbs = []
for d in g.COORDS_ORTH:
nb = add2(pos, d)
if nb[0] < 0 or nb[0] >= g.n_rows or nb[1] < 0 or nb[1] >= g.n_cols:
continue
if g[nb] == "#" or nb == trail[-2]:
continue
nbs.append(nb)
if len(nbs) == 1:
pos = nbs[0]
trail.append(pos)
else:
break
if not pos in sg:
sg[pos] = set()
dist = len(trail) - 1
sg[trail[0]].add((pos, dist))
sg[pos].add((trail[0], dist))
seen.add(pos)
for nb in nbs:
if not nb in seen:
seen.add(nb)
q.append([pos, nb])
# for key, value in sg.items():
# print(key, value)
# Brute force in bf order.
longest = 0
q = deque([(set(), start, 0)])
while q:
hist, pos, dist = q.popleft()
if pos == end:
if dist > longest:
longest = dist
continue
for nb, d in sg[pos]:
if nb in hist:
continue
nhist = hist.copy()
nhist.add(nb)
q.append((nhist, nb, dist + d))
return longest
def main():
DAY_INPUT = "i23.txt"
# print("Example 1:", solve(Input(EXAMPLE)))
# print("Solution 1:", solve(Input(DAY_INPUT)))
print("Example 2:", solve(Input(EXAMPLE), True))
print("Solution 2:", solve(Input(DAY_INPUT), True))
if __name__ == "__main__":
main()

74
d24.py Normal file
View File

@@ -0,0 +1,74 @@
from lib import *
import sympy as sp
EXAMPLE = """19, 13, 30 @ -2, 1, -2
18, 19, 22 @ -1, -1, -2
20, 25, 34 @ -2, -2, -4
12, 31, 28 @ -1, -2, -1
20, 19, 15 @ 1, -5, -3
"""
def solve1(input: Input):
if len(input.lines()) == 5:
lb = 7
ub = 27
else:
lb = 200000000000000
ub = 400000000000000
# On paper:
# (px - sx1) / vx1 = (py - sy1) / vy1
# (px - sx1) * vy1 = (py - sy1) * vx1
# (px - sx1) * vy1 - (py - sy1) * vx1 = 0
res = 0
eqs = [str_to_ints(l) for l in input.lines()]
for i, eq1 in enumerate(eqs):
for eq2 in eqs[:i]:
sx1, sy1, _, vx1, vy1, _ = eq1
sx2, sy2, _, vx2, vy2, _ = eq2
px, py = sp.symbols("px py")
es = [
vy1 * (px - sx1) - vx1 * (py - sy1),
vy2 * (px - sx2) - vx2 * (py - sy2),
]
r = sp.solve(es)
if not r:
continue
x, y = r[px], r[py]
if lb <= x <= ub and lb <= y < ub:
t1 = (x - sx1) / vx1
t2 = (x - sx2) / vx2
if (t1 > 0 and t2 > 0):
res += 1
return res
def solve2(input: Input):
eqs = [str_to_ints(l) for l in input.lines()]
px, py, pz, vxo, vyo, vzo = sp.symbols("px py pz vxo vyo vzo")
es = []
# The first six equations are enough to find a solution for my problem set.
# Might have to be increased depending on input.
for i, (x, y, z, vx, vy, vz) in enumerate(eqs[:6]):
t = sp.symbols(f"t{i}")
es.append(px + vxo * t - x - vx * t)
es.append(py + vyo * t - y - vy * t)
es.append(pz + vzo * t - z - vz * t)
r = sp.solve(es)[0]
return r[px] + r[py] + r[pz]
def main():
DAY_INPUT = "i24.txt"
print("Solution 1:", solve1(Input(EXAMPLE)))
print("Solution 1:", solve1(Input(DAY_INPUT)))
print("Example 2:", solve2(Input(EXAMPLE)))
print("Solution 2:", solve2(Input(DAY_INPUT)))
return
if __name__ == "__main__":
main()

87
d25.py Normal file
View File

@@ -0,0 +1,87 @@
from lib import *
from random import choice
from collections import deque
# def plot(graph):
# import networkx as nx
# import matplotlib
# import matplotlib.pyplot as plt
# G = nx.Graph()
# for node, connected_nodes in graph.items():
# for connected_node in connected_nodes:
# G.add_edge(node, connected_node)
# # pos = nx.spring_layout(G, k=2.0, iterations=20) # Adjust k as needed
# pos = nx.shell_layout(G)
# nx.draw(G, with_labels=True, node_color='lightblue', edge_color='gray', node_size=2000, font_size=15, font_weight='bold')
# matplotlib.use('qtagg')
# plt.show()
def solve(input: Input):
graph = {}
edges = {}
for line in input.lines():
src, dsts = line.split(":")
dsts = dsts.strip().split(" ")
if not src in graph:
graph[src] = []
for dst in dsts:
graph[src].append(dst)
if not dst in graph:
graph[dst] = []
graph[dst].append(src)
edge = tuple(sorted([src, dst]))
edges[edge] = 0
for _ in range(100):
first_node = choice(list(graph.keys()))
seen = set([first_node])
visit = deque([first_node])
while visit:
node = visit.popleft()
for nb in graph[node]:
if not nb in seen:
seen.add(nb)
visit.append(nb)
edge = tuple(sorted([node, nb]))
edges[edge] += 1
# Orignally, I used `plot(graph)` to visually find the nodes that have to
# be removed. I then came up with this heuristic approach. The idea is that
# we have to cross one of the three nodes when we do a breadth first
# search. By repeatedly doing that we can identify the "bridges" as the
# three edges that are used the most often.
most_visited = sorted(edges.items(), key=lambda t: t[1], reverse=True)[:3]
# to_remove = (("plt", "mgb"), ("jxm", "qns"), ("dbt", "tjd")) # found visually
# for node, count in most_visited:
# print(node, count) # should print the same as `to_remove`
for (a, b), _ in most_visited:
graph[a].remove(b)
graph[b].remove(a)
to_visit = [choice(list(graph.keys()))]
seen = set(to_visit)
while to_visit:
node = to_visit.pop()
for nb in graph[node]:
if not nb in seen:
seen.add(nb)
to_visit.append(nb)
return len(seen) * (len(graph) - len(seen))
def main():
DAY_INPUT = "i25.txt"
print("Solution 1:", solve(Input(DAY_INPUT)), "(hands-free)")
if __name__ == "__main__":
main()

4
d9.py
View File

@@ -9,7 +9,7 @@ EXAMPLE = """
def solve(lines: list[str]): def solve(lines: list[str]):
res = 0 res = 0
for (i, line) in enumerate(lines): for (i, line) in enumerate(lines):
digits = lib.str_to_int_list(line) digits = lib.str_to_ints(line)
last_digits = [] last_digits = []
while not all(d == 0 for d in digits): while not all(d == 0 for d in digits):
last_digits.append(digits[-1]) last_digits.append(digits[-1])
@@ -24,7 +24,7 @@ def solve(lines: list[str]):
def solve2(lines: list[str]): def solve2(lines: list[str]):
res = 0 res = 0
for (i, line) in enumerate(lines): for (i, line) in enumerate(lines):
digits = lib.str_to_int_list(line) digits = lib.str_to_ints(line)
first_digits = [] first_digits = []
while not all(d == 0 for d in digits): while not all(d == 0 for d in digits):
first_digits.append(digits[0]) first_digits.append(digits[0])

30
dx.py
View File

@@ -1,30 +0,0 @@
from lib import *
EXAMPLE = """
"""
def solve(i: Input, second=False):
res = 0
i.stats()
# g = i.grid2()
# ls = i.lines()
# ps = i.paras()
return res
def main():
DAY_INPUT = "ix.txt"
print("Example 1:", solve(Input(EXAMPLE)))
return
print("Solution 1:", solve(Input(DAY_INPUT)))
return
print("Example 2:", solve(Input(EXAMPLE), True))
return
print("Solution 2:", solve(Input(DAY_INPUT), True))
return
if __name__ == "__main__":
main()

1
dx.py Symbolic link
View File

@@ -0,0 +1 @@
../aocpy/dx.py

1
get.py Symbolic link
View File

@@ -0,0 +1 @@
../aocpy/get.py

12
get.sh
View File

@@ -1,12 +0,0 @@
#!/bin/bash
if [ "$#" -ne 1 ]; then
echo "Usage: $0 <day>"
exit 1
fi
DAY=$1
SESSION_COOKIE=$(keyring get aoc-session-cookie felixm)
echo $SESSION_COOKIE
curl "https://adventofcode.com/2023/day/$DAY/input" --cookie "session=$SESSION_COOKIE" > "i$DAY.txt"

235
lib.py
View File

@@ -1,235 +0,0 @@
import re
import os
import string
import heapq
NUMBERS = string.digits
LETTERS_LOWER = string.ascii_lowercase
LETTERS_UPPER = string.ascii_uppercase
INF = float("inf")
fst = lambda l: l[0]
snd = lambda l: l[1]
def maps(f, xs):
if isinstance(xs, list):
return [maps(f, x) for x in xs]
return f(xs)
def mape(f, xs):
return list(map(f, xs))
def add2(a: tuple[int, int], b: tuple[int, int]) -> tuple[int, int]:
return (a[0] + b[0], a[1] + b[1])
class Grid2D:
N = (-1, 0)
E = (0, 1)
S = (1, 0)
W = (0, -1)
NW = (-1, -1)
NE = (-1, 1)
SE = (1, 1)
SW = (1, -1)
COORDS_ORTH = (N, E, S, W)
COORDS_DIAG = (NW, NE, SE, SW)
def __init__(self, text: str):
lines = [line for line in text.splitlines() if line.strip() != ""]
self.grid = list(map(list, lines))
self.n_rows = len(self.grid)
self.n_cols = len(self.grid[0])
def __getitem__(self, pos: tuple[int, int]):
row, col = pos
return self.grid[row][col]
def __setitem__(self, pos: tuple[int, int], val):
row, col = pos
self.grid[row][col] = val
def clone_with_val(self, val):
c = Grid2D("d\nd")
c.n_rows = self.n_rows
c.n_cols = self.n_cols
c.grid = [[val for _ in range(c.n_cols)]
for _ in range(self.n_rows)]
return c
def rows(self) -> list[list[str]]:
return [row for row in self.grid]
def cols(self) -> list[list[str]]:
rows = self.rows()
return [[row[col_i] for row in rows]
for col_i in range(self.n_cols)]
def find(self, chars: str) -> list[tuple[int, int]]:
return [c for c in self.all_coords() if self[c] in chars]
def find_not(self, chars: str) -> list[tuple[int, int]]:
return [c for c in self.all_coords() if self[c] not in chars]
def all_coords(self) -> list[tuple[int, int]]:
return [(row_i, col_i)
for row_i in range(self.n_rows)
for col_i in range(self.n_cols)]
def row_coords(self, row_i) -> list[tuple[int, int]]:
assert row_i < self.n_rows, f"{row_i=} must be smaller than {self.n_rows=}"
return [(col_i, row_i) for col_i in range(self.n_cols)]
def col_coords(self, col_i) -> list[tuple[int, int]]:
assert col_i < self.n_cols, f"{col_i=} must be smaller than {self.n_cols=}"
return [(col_i, row_i) for row_i in range(self.n_rows)]
def contains(self, pos: tuple[int, int]) -> bool:
row, col = pos
return row >= 0 and row < self.n_rows and col >= 0 and col < self.n_cols
def __contains__(self, pos: tuple[int, int]) -> bool:
return self.contains(pos)
def neighbors_ort(self, pos: tuple[int, int]) -> list[tuple[int, int]]:
return [add2(pos, off) for off in self.dirs_ort() if self.contains(add2(pos, off))]
def neighbors_vert(self, pos: tuple[int, int]) -> list[tuple[int, int]]:
return [add2(pos, off) for off in self.dirs_vert() if self.contains(add2(pos, off))]
def neighbors_adj(self, pos: tuple[int, int]) -> list[tuple[int, int]]:
return self.neighbors_ort(pos) + self.neighbors_vert(pos)
def flip_ort(self, pos: tuple[int, int]) -> tuple[int, int]:
return (-pos[0], -pos[1])
def dirs_ort(self) -> list[tuple[int, int]]:
return [self.N, self.E, self.S, self.W]
def dirs_vert(self) -> list[tuple[int, int]]:
return [self.NE, self.SE, self.SW, self.NW]
def print(self):
for r in self.rows():
print("".join(r))
def print_with_gaps(self):
for r in self.rows():
print(" ".join(map(str, r)))
class Input:
def __init__(self, text: str):
if os.path.isfile(text):
self.text = open(text).read()
else:
self.text = text
def stats(self):
print(f" size: {len(self.text)}")
print(f"lines: {len(self.text.splitlines())}")
ps = len(self.paras())
print(f"paras: {ps}")
def lines(self) -> list[str]:
return self.text.splitlines()
def paras(self) -> list[list[str]]:
return [p for p in self.text.split("\n\n")]
def grid2(self) -> Grid2D:
return Grid2D(self.text)
def prime_factors(n):
"""
Returns a list of prime factors for n.
:param n: number for which prime factors should be returned
"""
factors = []
rest = n
divisor = 2
while rest % divisor == 0:
factors.append(divisor)
rest //= divisor
divisor = 3
while divisor * divisor <= rest:
while rest % divisor == 0:
factors.append(divisor)
rest //= divisor
divisor += 2
if rest != 1:
factors.append(rest)
return factors
def lcm(numbers: list[int]) -> int:
fs = []
for n in numbers:
fs += prime_factors(n)
s = 1
fs = list(set(fs))
for f in fs:
s *= f
return s
def str_to_int(line: str) -> int:
line = line.replace(" ", "")
r = re.compile(r"-?\d+")
m = r.findall(line)
assert len(m) == 0, "str_to_int no int"
assert len(m) > 1, "str_to_int multiple ints"
return int(m[0])
def str_to_ints(line: str) -> list[int]:
r = re.compile(r"-?\d+")
return list(map(int, r.findall(line)))
def str_to_lines_no_empty(text: str) -> list[str]:
return list(filter(lambda l: l.strip() != "", text.splitlines()))
def str_to_lines(text: str) -> list[str]:
return list(text.splitlines())
def count_trailing_repeats(lst):
count = 0
for elem in reversed(lst):
if elem != lst[-1]:
break
else:
count += 1
return count
class A_Star(object):
def __init__(self, starts, is_goal, h, d, neighbors):
"""
:param h: heuristic function
:param d: cost from node to node function
:param neighbors: neighbors function
"""
open_set = []
g_score = {}
for start in starts:
heapq.heappush(open_set, (h(start), start))
g_score[start] = d(0, start)
while open_set:
current_f_score, current = heapq.heappop(open_set)
if is_goal(current):
self.cost = current_f_score
break
for neighbor in neighbors(current):
tentative_g_score = g_score[current] + d(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))
def shoelace_area(corners):
n = len(corners)
area = 0
for i in range(n):
x1, y1 = corners[i]
x2, y2 = corners[(i + 1) % n]
area += (x1 * y2) - (x2 * y1)
return abs(area) / 2.0

1
lib.py Symbolic link
View File

@@ -0,0 +1 @@
../aocpy/lib.py

View File

@@ -1,39 +0,0 @@
#!/usr/bin/env python
import hashlib
import time
import subprocess
import sys
def get_file_hash(filename):
hasher = hashlib.sha256()
with open(filename, 'rb') as f:
hasher.update(f.read())
return hasher.hexdigest()
def main(script_name, interval=1):
last_hash = None
process = None
while True:
try:
current_hash = get_file_hash(script_name)
if current_hash != last_hash:
last_hash = current_hash
if process and process.poll() is None:
process.terminate()
print(f"Detected change in {script_name}, running script...")
process = subprocess.Popen(['pypy3', script_name], shell=False)
time.sleep(interval)
except KeyboardInterrupt:
if process:
process.terminate()
break
except FileNotFoundError:
print("The file was not found. Make sure the script name is correct.")
break
if __name__ == "__main__":
main(sys.argv[1])

1
monitor.py Symbolic link
View File

@@ -0,0 +1 @@
../aocpy/monitor.py