Compare commits

..

No commits in common. "178d96494a26cc16952734eb7e1f4b25059b119a" and "08288964bdcbd47d11343eb0bac69d3f640f8c3b" have entirely different histories.

20 changed files with 113 additions and 712 deletions

View file

@ -36,7 +36,3 @@ Execute separate puzzle on file save (replace `XX` with the puzzle number):
(requires `entr` and `wl-paste`, Mac users can instead use `pbpaste`. If you (requires `entr` and `wl-paste`, Mac users can instead use `pbpaste`. If you
prefer X at Linux, use `xclip -selection clipboard -o`). prefer X at Linux, use `xclip -selection clipboard -o`).
To lint files:
ls output/*.py | entr -r -c flake8 output --ignore=E741,E501,E203

View file

@ -56,8 +56,8 @@ if __name__ == "__main__":
# import sys; inp = sys.stdin.read().strip() # import sys; inp = sys.stdin.read().strip()
# uncomment to use AoC provided puzzle input # uncomment to use AoC provided puzzle input
# with open("./input/{padded_no}.txt", "r") as f: # with open(f"./input/{padded_no}.txt", "r") as f:
# inp = f.read().strip() # inp = f.read()
# uncomment to do initial data processing shared by part 1-2 # uncomment to do initial data processing shared by part 1-2
inp = solve(inp) inp = solve(inp)

View file

@ -80,59 +80,3 @@ def vdbg(seen, h, w):
"""Print-debug visited positions of a matrix""" """Print-debug visited positions of a matrix"""
for r in range(h): for r in range(h):
print("".join(["#" if (r, c) in seen else "." for c in range(w)])) print("".join(["#" if (r, c) in seen else "." for c in range(w)]))
"""
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
isnt 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.
"""
# https://pieriantraining.com/understanding-dijkstras-algorithm-in-python/
def dijkstra_algorithm(graph, start_node):
def min_distance(distances, visited):
min_val = float("inf")
min_index = -1
for i in range(len(distances)):
if distances[i] < min_val and i not in visited:
min_val = distances[i]
min_index = i
return min_index
num_nodes = len(graph)
distances = [float("inf")] * num_nodes
visited = []
distances[start_node] = 0
for i in range(num_nodes):
current_node = min_distance(distances, visited)
visited.append(current_node)
for j in range(num_nodes):
if graph[current_node][j] != 0:
new_distance = distances[current_node] + graph[current_node][j]
if new_distance < distances[j]:
distances[j] = new_distance
return distances

View file

@ -41,7 +41,7 @@ def part_2(data):
if __name__ == "__main__": if __name__ == "__main__":
with open("./input/01.txt", "r") as f: with open(f"./input/01.txt", "r") as f:
inp = f.read().strip() inp = f.read().strip()
a = part_1(inp) a = part_1(inp)

View file

@ -39,7 +39,7 @@ def part_2(data):
if __name__ == "__main__": if __name__ == "__main__":
with open("./input/02.txt", "r") as f: with open(f"./input/02.txt", "r") as f:
inp = f.read().strip() inp = f.read().strip()
a = part_1(inp) a = part_1(inp)

View file

@ -56,7 +56,7 @@ def presolve(data):
if __name__ == "__main__": if __name__ == "__main__":
with open("./input/03.txt", "r") as f: with open(f"./input/03.txt", "r") as f:
inp = f.read().strip() inp = f.read().strip()
parsed = presolve(inp) parsed = presolve(inp)

View file

@ -35,7 +35,7 @@ def presolve(data):
if __name__ == "__main__": if __name__ == "__main__":
with open("./input/04.txt", "r") as f: with open(f"./input/04.txt", "r") as f:
inp = f.read().strip() inp = f.read().strip()
inp = presolve(inp) inp = presolve(inp)

View file

@ -1,5 +1,7 @@
import re import re
from itertools import repeat
from math import inf from math import inf
from multiprocessing import Pool, freeze_support
from output import answer from output import answer
n = 5 n = 5
@ -7,67 +9,91 @@ title = "If You Give A Seed A Fertilizer"
@answer(1, "Nearest location for seed id list is {}") @answer(1, "Nearest location for seed id list is {}")
def part_1(presolved): def part_1(data):
l, _ = presolved seeds, *process = data.split("\n\n")
return l seeds = [f"{v} 1" for v in seeds.split()[1:]]
return _bruteforce(seeds, process, 1)
@answer(2, "Interpreting ranges of seeds, nearest location is {}") @answer(2, "Interpreting ranges of seeds, nearest location is {}")
def part_2(presolved): def part_2(data):
_, l = presolved
return l
def presolve(data):
seeds, *process = data.split("\n\n") seeds, *process = data.split("\n\n")
seed_ranges = [[int(x) for x in ar.split()] for ar in re.findall(r"\d+ \d+", seeds)] seeds = re.findall(r"\d+ \d+", seeds)
seed_values = [int(v) for v in seeds.split()[1:]] return _bruteforce(seeds, process, 8)
processes = [
[tuple(map(int, line.split())) for line in step.splitlines()[1:]]
for step in process
]
p1 = _process(seed_values, processes)
p2 = 26829000 # takes 5m if starting from 0
while True:
g = _process([p2], processes, reverse=True)
if any(g >= a and g < a + r for a, r in seed_ranges):
break
p2 += 1
return p1, p2
def _process(seeds, processes, reverse=False): def _bruteforce(seeds, process, p=1):
n = inf processes = [[tuple(map(int, line.split())) for line in step.splitlines()[1:]] for step in process]
for start in seeds:
n = min(n, _nearest(start, processes, reverse=reverse)) sm = []
return n for start_r in seeds:
pool = Pool()
start, r = start_r.split()
d = int(r) // p
parts = [(d * n + int(start), d * n + int(start) + d) for n in range(p)]
sm += pool.starmap(_nearest, zip(parts, repeat(processes)))
return min(sm)
def _nearest(start, processes, reverse=False): def _nearest(start_r, processes):
procs = processes if not reverse else processes[::-1] a, b = start_r
v = start nearest = inf
for steps in procs: for i in range(a, b):
v = i
for steps in processes:
nid = -1
for line in steps: for line in steps:
dest, src, r = line dest, src, r = line
if reverse:
dest, src = src, dest
if v >= src and v < src + r: if v >= src and v < src + r:
v = dest + v - src v = dest + v - src
break break
return v nearest = min(nearest, v)
return nearest
if __name__ == "__main__": if __name__ == "__main__":
with open("./input/05.txt", "r") as f: # use dummy data
inp = f.read().strip() inp = """
seeds: 79 14 55 13
inp = presolve(inp) seed-to-soil map:
50 98 2
52 50 48
soil-to-fertilizer map:
0 15 37
37 52 2
39 0 15
fertilizer-to-water map:
49 53 8
0 11 42
42 0 7
57 7 4
water-to-light map:
88 18 7
18 25 70
light-to-temperature map:
45 77 23
81 45 19
68 64 13
temperature-to-humidity map:
0 69 1
1 0 69
humidity-to-location map:
60 56 37
56 93 4
""".strip()
# inp = parse_input()
a = part_1(inp) a = part_1(inp)
b = part_2(inp) b = part_2(inp)
assert a == 278755257 # assert a == 278755257
assert b == 26829166 # assert b == 26829166

View file

@ -35,7 +35,7 @@ def presolve(data):
if __name__ == "__main__": if __name__ == "__main__":
with open("./input/06.txt", "r") as f: with open(f"./input/06.txt", "r") as f:
inp = f.read().strip() inp = f.read().strip()
inp = presolve(inp) inp = presolve(inp)

View file

@ -84,7 +84,7 @@ if __name__ == "__main__":
doctest.testmod() doctest.testmod()
with open("./input/07.txt", "r") as f: with open(f"./input/07.txt", "r") as f:
inp = f.read().strip() inp = f.read().strip()
a = part_1(inp) a = part_1(inp)

View file

@ -49,7 +49,7 @@ def presolve(data):
if __name__ == "__main__": if __name__ == "__main__":
with open("./input/08.txt", "r") as f: with open(f"./input/08.txt", "r") as f:
inp = f.read().strip() inp = f.read().strip()
inp = presolve(inp) inp = presolve(inp)

View file

@ -30,7 +30,7 @@ def _solve(lines):
if __name__ == "__main__": if __name__ == "__main__":
with open("./input/09.txt", "r") as f: with open(f"./input/09.txt", "r") as f:
inp = f.read().strip() inp = f.read().strip()
a = part_1(inp) a = part_1(inp)

View file

@ -1,94 +1,44 @@
from collections import deque
from output import answer from output import answer
n = 10 n = 10
title = "Pipe Maze" title = "dddd"
D = (-1, 0), (0, 1), (1, 0), (0, -1) @answer(1, "Answer is {}")
def part_1(data):
C = { return data
(-1, 0): ["|", "7", "F"],
(0, 1): ["-", "7", "J"],
(1, 0): ["|", "L", "J"],
(0, -1): ["-", "L", "F"],
}
A = {
"S": [0, 1, 2, 3],
"-": [1, 3],
"|": [0, 2],
"F": [1, 2],
"L": [0, 1],
"7": [2, 3],
"J": [0, 3],
}
@answer(1, "Farthest away pipe is at {}") @answer(2, "Actually, answer is {}")
def part_1(presolved): def part_2(data):
return presolved[0] return data
@answer(2, "{} spots are encapsulated by pipes") # uncomment to solve parts in one go
def part_2(presolved): # def presolve(data):
return presolved[1] # return data
def presolve(data):
matrix = data.split()
w = len(matrix[0])
h = len(matrix)
q = deque()
visited = set()
for r in range(h):
if "S" in matrix[r]:
start = (r, matrix[r].index("S"))
q.append(start)
break
while q:
o = q.popleft()
visited.add(o)
for di in A[matrix[o[0]][o[1]]]:
d = D[di]
r = o[0] + d[0]
c = o[1] + d[1]
if r >= 0 and r < h and c >= 0 and c < w:
t = matrix[r][c]
p = (r, c)
if p not in visited and t != "." and t in C[d]:
q.append(p)
p1 = len(visited) // 2
p2 = 0
for y in range(h):
for x in range(w):
if (y, x) in visited:
continue
crosses = 0
y2, x2 = y, x
while y2 < h and x2 < w:
c2 = matrix[y2][x2]
if (y2, x2) in visited and c2 not in "L7":
crosses += 1
x2 += 1
y2 += 1
if crosses % 2 == 1:
p2 += 1
return p1, p2
if __name__ == "__main__": if __name__ == "__main__":
with open("./input/10.txt", "r") as f: # use dummy data
inp = f.read() inp = """
replace me
""".strip()
inp = presolve(inp) # uncomment to instead use stdin
# import sys; inp = sys.stdin.read().strip()
# uncomment to use AoC provided puzzle input
# with open(f"./input/10.txt", "r") as f:
# inp = f.read()
# uncomment to do initial data processing shared by part 1-2
# inp = presolve(inp)
a = part_1(inp) a = part_1(inp)
b = part_2(inp) # b = part_2(inp)
assert a == 6846 # uncomment and replace 0 with actual output to refactor code
assert b == 325 # and ensure nonbreaking changes
# assert a == 0
# assert b == 0

View file

@ -1,63 +0,0 @@
from itertools import combinations
from output import answer
n = 11
title = "Cosmic Expansion"
@answer(1, "Sum of all galaxy shortest distances is {}")
def part_1(data):
return data[0]
@answer(2, "Exapanding by 1M, sum is {}")
def part_2(data):
return data[1]
def presolve(data):
m = data.splitlines()
er = set()
ec = set()
for i, r in enumerate(m):
if "#" not in r:
er.add(i)
for i, c in enumerate(zip(*m)):
if "#" not in c:
ec.add(i)
h = len(m)
w = len(m[0])
g1 = []
g2 = []
e = 1e6
for r in range(h):
for c in range(w):
if m[r][c] == "#":
ro = len(er & set(range(r)))
co = len(ec & set(range(c)))
g1.append((r + ro, c + co))
g2.append((ro * e + r - ro, co * e + c - co))
p1 = sum(
abs(rc1[0] - rc2[0]) + abs(rc1[1] - rc2[1]) for rc1, rc2 in combinations(g1, 2)
)
p2 = int(
sum(
abs(rc1[0] - rc2[0]) + abs(rc1[1] - rc2[1])
for rc1, rc2 in combinations(g2, 2)
)
)
return p1, p2
if __name__ == "__main__":
with open("./input/11.txt", "r") as f:
inp = f.read().strip()
inp = presolve(inp)
a = part_1(inp)
b = part_2(inp)
assert a == 9370588
assert b == 746207878188

View file

@ -1,64 +0,0 @@
from functools import cache
from output import answer
n = 12
title = "Hot Springs"
@answer(1, "sum of all possible combinations is {}")
def part_1(presolved):
return presolved[0]
@answer(2, "sum of all possible combinations is {} when unfolded")
def part_2(presolved):
return presolved[1]
def presolve(data):
lines = [
(a, list(map(int, b.split(","))))
for a, b in (line.split() for line in data.splitlines())
]
p1 = sum(_inspect(a, tuple(b)) for a, b in lines)
p2 = sum(_inspect("?".join([a] * 5), tuple(b * 5)) for a, b in lines)
return p1, p2
@cache
def _inspect(s, cs):
r = len(s)
csl = len(cs)
if r == 0:
return 1 if csl == 0 else 0
o, *f = s
f = "".join(f)
if o == ".":
return _inspect(f, cs)
if o == "?":
return _inspect("." + f, cs) + _inspect("#" + f, cs)
if not csl:
return 0
g = cs[0]
if g > r or "." in s[0:g]:
return 0
elif csl > 1:
if g + 1 > r or s[g] == "#":
return 0
else:
return _inspect(s[g + 1 :], cs[1:])
elif csl == 1:
return _inspect(s[g:], ())
if __name__ == "__main__":
with open("./input/12.txt", "r") as f:
inp = f.read().strip()
inp = presolve(inp)
a = part_1(inp)
b = part_2(inp)
assert a == 7118
assert b == 7030194981795

View file

@ -1,56 +0,0 @@
from output import answer
n = 13
title = "Point of Incidence"
@answer(1, "Summarizing the notes gives {}")
def part_1(presolved):
return presolved[0]
@answer(2, "Summarizing the notes allowing off-by-1 gives {}")
def part_2(presolved):
return presolved[1]
def presolve(data):
g = [l.split() for l in data.split("\n\n")]
p1 = sum(d * n for d, n in _inspect(g))
p2 = sum(d * n for d, n in _inspect(g, 1))
return p1, p2
def _inspect(g, a=0):
af = []
for m in g:
for d, n in [(100, m), (1, tuple(zip(*m)))]:
af.append((d, _compare(n, a)))
return af
def _compare(l, a=0):
for i in range(1, len(l)):
if (
sum(
sum(a != b for a, b in zip(x, y)) for x, y in zip(l[i - 1 :: -1], l[i:])
)
== a
):
return i
return 0
if __name__ == "__main__":
with open("./input/13.txt", "r") as f:
inp = f.read().strip()
inp = presolve(inp)
a = part_1(inp)
b = part_2(inp)
assert a == 29213
assert b == 37453

View file

@ -1,83 +0,0 @@
from output import answer
n = 14
title = "Parabolic Reflector Dish"
@answer(1, "Total initial load on the northern beams: {}")
def part_1(presolved):
return presolved[0]
@answer(2, "After some humble load testing, the northern beam load is {}")
def part_2(presolved):
return presolved[1]
BAEST = 1000_000_000
def presolve(data):
m = [list(l) for l in data.split()]
s = len(m[0])
m1 = _tilt(m)
p1 = sum(sum((s - w) * o.count("O") for o in r) for w, r in enumerate(m1))
def impl(rc):
return "".join(["".join(r) for r in rc])
i = 0
seen = []
while True:
i += 1
for _ in range(4):
m = _tilt(m)
m = _rotate(m)
im = impl(m)
if im in seen:
break
else:
seen.append(im)
m2 = m
c = seen.index(im) + 1
for _ in range((BAEST - i) % (i - c)):
for j in range(4):
m2 = _tilt(m2)
m2 = _rotate(m2)
p2 = sum(sum((s - w) * o.count("O") for o in r) for w, r in enumerate(m2))
return p1, p2
def _rotate(m):
return [list(l) for l in zip(*m[::-1])]
def _tilt(m):
m = [list(l) for l in zip(*m)]
h = len(m[0])
for c in m:
u = True
while u:
u = False
for i in range(h - 1):
j = i + 1
if c[i] == "#" or c[j] == "#":
continue
if c[i] < c[j]:
c[j], c[i] = c[i], c[j]
u = True
return [list(l) for l in zip(*m)]
if __name__ == "__main__":
with open("./input/14.txt", "r") as f:
inp = f.read().strip()
inp = presolve(inp)
a = part_1(inp)
b = part_2(inp)
assert a == 109596
assert b == 96105

View file

@ -1,63 +0,0 @@
from collections import OrderedDict, defaultdict
from output import answer
n = 15
title = "Lens Library"
@answer(1, "Sum of HASH algorithm results: {}")
def part_1(presolved):
return presolved[0]
@answer(2, "Focusing power of the resulting configuration: {}")
def part_2(presolved):
return presolved[1]
def presolve(data):
def h(s):
v = 0
for a in s:
if a == "\n":
continue
v += ord(a)
v *= 17
v = v % 256
return v
p1 = sum(h(c) for c in data.split(","))
b = defaultdict(OrderedDict)
for lr in data.split(","):
if "=" in lr:
l, r = lr.split("=")
if r == "":
continue
k = h(l)
b[k][l] = r
if "-" in lr:
l, _r = lr.split("-")
k = h(l)
if l in b[k]:
del b[k][l]
p2 = 0
for i, c in b.items():
for j, f in enumerate(b[i].values(), 1):
p2 += (i + 1) * j * int(f)
return p1, p2
if __name__ == "__main__":
with open("./input/16.txt", "r") as f:
inp = f.read().strip()
inp = presolve(inp)
a = part_1(inp)
b = part_2(inp)
assert a == 509784
assert b == 230197

View file

@ -1,86 +0,0 @@
from itertools import chain
from output import D, answer, matrix
n = 16
title = "The Floor Will Be Lava"
@answer(1, "Energized tiles count, starting at top-left facing right: {}")
def part_1(presolved):
return presolved[0]
@answer(2, "Max energized tiles count, starting from all edges: {}")
def part_2(presolved):
return presolved[1]
def presolve(data):
m, w, h = matrix(data)
p1 = 0
p2 = 0
for sp in chain(
[(h - 1, n, 0) for n in range(w)],
[(n, 0, 1) for n in range(h)],
[(0, n, 2) for n in range(w)],
[(n, w - 1, 3) for n in range(h)],
):
q = [sp]
seen = set()
while q:
rcd = q.pop(0)
if (rcd) in seen:
continue
r, c, d = rcd
if r < 0 or r >= h or c < 0 or c >= w:
continue
seen.add((r, c, d))
match m[r][c]:
case ".":
o1, o2 = D[d]
q.append((o1 + r, o2 + c, d))
case "|":
if d in [0, 2]:
o1, o2 = D[d]
q.append((o1 + r, o2 + c, d))
else:
for d in [(d - 1) % 4, (d + 1) % 4]:
o1, o2 = D[d]
q.append((o1 + r, o2 + c, d))
case "-":
if d in [1, 3]:
o1, o2 = D[d]
q.append((o1 + r, o2 + c, d))
else:
for d in [(d - 1) % 4, (d + 1) % 4]:
o1, o2 = D[d]
q.append((o1 + r, o2 + c, d))
case "\\":
d += 1 if d in [1, 3] else -1
d = d % 4
o1, o2 = D[d]
q.append((o1 + r, o2 + c, d))
case "/":
d += 1 if d in [0, 2] else -1
d = d % 4
o1, o2 = D[d % 4]
q.append((o1 + r, o2 + c, d))
b = len(set([(r, c) for r, c, d in seen]))
if sp == (0, 0, 1):
p1 = b
p2 = max(p2, b)
return p1, p2
if __name__ == "__main__":
with open("./input/16.txt", "r") as f:
inp = f.read().strip()
inp = presolve(inp)
a = part_1(inp)
b = part_2(inp)
assert a == 7884
assert b == 8185

View file

@ -1,100 +0,0 @@
from heapq import heappop, heappush
from output import answer # D, DD, ADJ, ints, mhd, mdbg, vdbg
n = 17
title = "Clumsy Crucible"
@answer(1, "Using std crucible, least heat loss incured: {}")
def part_1(presolved):
return presolved[0]
@answer(2, "Using ultra crucible, least heat loss incured: {}")
def part_2(presolved):
return presolved[1]
def solve(data):
grid = {
(r, c): int(col)
for r, row in enumerate(data.split())
for c, col in enumerate(row)
}
p1 = least_heat_loss(grid, 1, 3)
p2 = least_heat_loss(grid, 4, 10)
return p1, p2
def least_heat_loss(grid, minsteps, maxsteps):
target = max(grid)
seen = set()
queue = [(0, (0, 0), (0, 1), 0)]
while queue:
cost, pos, direction, steps = heappop(queue)
y, x = pos
dy, dx = direction
if pos == target:
return cost
if ((pos, direction, steps)) in seen:
continue
seen.add((pos, direction, steps))
if steps >= minsteps:
cwdy, cwdx = clockwise(*direction)
if (cw := (y + cwdy, x + cwdx)) in grid:
cwy, cwx = cw
heappush(queue, (cost + grid[cw], (cwy, cwx), (cwdy, cwdx), 1))
ccwdy, ccwdx = counterclockwise(*direction)
if (ccw := (y + ccwdy, x + ccwdx)) in grid:
ccwy, ccwx = ccw
heappush(queue, (cost + grid[ccw], (ccwy, ccwx), (ccwdy, ccwdx), 1))
if steps < maxsteps and (fwd := (y + dy, x + dx)) in grid:
fwdy, fwdx = fwd
heappush(queue, (cost + grid[fwd], (fwdy, fwdx), direction, steps + 1))
return -1
def clockwise(y, x):
"""
>>> clockwise(-1, 0)
(0, 1)
>>> clockwise(0, 1)
(1, 0)
>>> clockwise(1, 0)
(0, -1)
>>> clockwise(0, -1)
(-1, 0)
"""
return (x, y) if y == 0 else (x, -y)
def counterclockwise(y, x):
"""
>>> counterclockwise(-1, 0)
(0, -1)
>>> counterclockwise(0, -1)
(1, 0)
>>> counterclockwise(1, 0)
(0, 1)
>>> counterclockwise(0, 1)
(-1, 0)
"""
return (x, y) if x == 0 else (-x, y)
if __name__ == "__main__":
with open("./input/17.txt", "r") as f:
inp = f.read().strip()
inp = solve(inp)
a = part_1(inp)
b = part_2(inp)