Compare commits

...

2 Commits

Author SHA1 Message Date
a2a6ca52d1 Solve 2021 day 20, 21, 23, 25 2024-11-27 17:31:52 -05:00
4acc229c5a Solve 2021 day 18 2024-11-24 18:50:28 -05:00
9 changed files with 522 additions and 18 deletions

115
2021/d18.py Normal file
View File

@ -0,0 +1,115 @@
from lib import get_data
from math import ceil, floor
from itertools import combinations
data = """[[[0,[5,8]],[[1,7],[9,6]]],[[4,[1,2]],[[1,4],2]]]
[[[5,[2,8]],4],[5,[[9,9],0]]]
[6,[[[6,2],[5,6]],[[7,6],[4,7]]]]
[[[6,[0,7]],[0,9]],[4,[9,[9,0]]]]
[[[7,[6,4]],[3,[1,3]]],[[[5,5],1],9]]
[[6,[[7,3],[3,2]]],[[[3,8],[5,7]],4]]
[[[[5,4],[7,7]],8],[[8,3],8]]
[[9,3],[[9,9],[6,[4,9]]]]
[[2,[[7,7],7]],[[5,8],[[9,3],[0,2]]]]
[[[[5,2],5],[8,[3,7]]],[[5,[7,5]],[4,4]]]"""
data = get_data(__file__)
def parse(line):
xs = list(line)
for i in range(len(xs)):
try:
xs[i] = int(xs[i])
except ValueError:
pass
assert line == "".join(map(str, xs))
return xs
def reduce(xs):
reduced, nesting = False, 0
for i in range(len(xs)):
c = xs[i]
if c == "[" and nesting == 4 and not reduced:
lhs, rhs = xs[i + 1], xs[i + 3]
if not (type(lhs) is int and type(rhs) is int):
continue
xs = xs[:i] + [0] + xs[i + 5 :]
for j in range(i - 1, -1, -1):
if type(xs[j]) is int:
xs[j] += lhs
break
for j in range(i + 1, len(xs)):
if type(xs[j]) is int:
xs[j] += rhs
break
reduced = True
break
elif c == "[":
nesting += 1
elif c == "]":
nesting -= 1
if not reduced:
for i in range(len(xs)):
c = xs[i]
if type(c) is int and c >= 10:
p = [floor(c / 2), ceil(c / 2)]
xs = xs[:i] + ["[", p[0], ",", p[1], "]"] + xs[i + 1 :]
reduced = True
break
return xs
def restore(xs):
return "".join(map(str, xs))
assert restore(reduce(parse("[[[[[9,8],1],2],3],4]"))) == "[[[[0,9],2],3],4]"
assert restore(reduce(parse("[7,[6,[5,[4,[3,2]]]]]"))) == "[7,[6,[5,[7,0]]]]"
assert (
restore(reduce(parse("[[3,[2,[1,[7,3]]]],[6,[5,[4,[3,2]]]]]")))
== "[[3,[2,[8,0]]],[9,[5,[4,[3,2]]]]]"
)
assert (
restore(reduce(parse("[[3,[2,[8,0]]],[9,[5,[4,[3,2]]]]]")))
== "[[3,[2,[8,0]]],[9,[5,[7,0]]]]"
)
def add(a, b):
s = ["["] + a + [","] + b + ["]"]
while True:
ns = reduce(s)
if s == ns:
break
s = ns
return s
a = "[[[[4,3],4],4],[7,[[8,4],9]]]"
b = "[1,1]"
assert restore(add(parse(a), parse(b))) == "[[[[0,7],4],[[7,8],[6,0]]],[8,1]]"
lines = list(data.splitlines())
s = parse(lines[0].strip())
for line in lines[1:]:
s = add(s, parse(line.strip()))
def get_mag(xs):
if type(xs) is int:
return xs
return get_mag(xs[0]) * 3 + get_mag(xs[1]) * 2
print(get_mag(eval(restore(s))))
lines = list(data.splitlines())
m = 0
for a, b in combinations(lines, 2):
m = max(m, get_mag(eval(restore(add(parse(a), parse(b))))))
m = max(m, get_mag(eval(restore(add(parse(b), parse(a))))))
print(m)

51
2021/d20.py Normal file
View File

@ -0,0 +1,51 @@
from lib import get_data
data = get_data(__file__)
for rounds in [2, 50]:
algo, img = data.strip().split("\n\n")
algo = algo.replace("\n", "")
xs = set()
img = img.splitlines()
for row in range(len(img)):
for col in range(len(img[0])):
if img[row][col] == "#":
xs.add((row, col))
for i in range(rounds):
y_min = min(v[0] for v in xs) - 6
y_max = max(v[0] for v in xs) + 6
x_min = min(v[1] for v in xs) - 6
x_max = max(v[1] for v in xs) + 6
nxs = set()
for row in range(y_min, y_max + 1):
for col in range(x_min, x_max + 1):
bin = ""
for ro in range(3):
for co in range(3):
if (row + ro, col + co) in xs:
bin += "1"
else:
bin += "0"
bin = int(bin, 2)
if algo[bin] == "#":
nxs.add((row, col))
xs = nxs
y_min_trim = y_min + 7
y_max_trim = y_max - 8
x_min_trim = x_min + 7
x_max_trim = x_max - 8
# special clean-up required because input creates pixels when there
# are no other pixels around (0b0_0000_0000)
if i % 2 == 1:
xs = [
(r, c)
for (r, c) in xs
if y_min_trim <= r <= y_max_trim and x_min_trim <= c <= x_max_trim
]
print(len(xs))

65
2021/d21.py Normal file
View File

@ -0,0 +1,65 @@
from lib import get_data
from lib import ints
from collections import defaultdict
FIELD = 10
DICE = 100
dice = -1
data = get_data(__file__)
_, p1, _, p2 = ints(data)
pos = [p1 - 1, p2 - 1]
scores = [0, 0]
dice_rolls = 0
not_done = True
while not_done:
for p in range(len(scores)):
dice_rolls += 3
for _ in range(3):
dice += 1 % DICE
pos[p] = (pos[p] + (dice + 1)) % FIELD
scores[p] += pos[p] + 1
if scores[p] >= 1000:
not_done = False
break
r = min(scores) * dice_rolls
print(r)
_, p1, _, p2 = ints(data)
pos = (p1 - 1, p2 - 1)
scores = (0, 0)
unis = defaultdict(int)
won = [0, 0]
unis[(0, pos, scores)] += 1
while unis:
nunis = defaultdict(int)
for (turn, opos, scores), count in unis.items():
other = (turn + 1) % 2
poss = [opos[turn]]
for _ in range(3):
nposs = []
for dice in range(1, 3 + 1):
for pos in poss:
npos = (pos + dice) % FIELD
nposs.append(npos)
poss = nposs
for npos_player in poss:
score = scores[turn] + npos_player + 1
if score >= 21:
won[turn] += count
else:
nscores = list(scores)
nscores[turn] = score
npos = list(opos)
npos[turn] = npos_player
state = (other, tuple(npos), tuple(nscores))
nunis[state] += count
unis = nunis
print(max(won))

27
2021/d22.py Normal file
View File

@ -0,0 +1,27 @@
from lib import get_data
from lib import ints
data = get_data(__file__)
limit = 50
ps = set()
for i, line in enumerate(data.splitlines()):
x_min, x_max, y_min, y_max, z_min, z_max = ints(line)
for x in range(max(x_min, -limit), min(x_max, limit) + 1):
for y in range(max(y_min, -limit), min(y_max, limit) + 1):
for z in range(max(z_min, -limit), min(z_max, limit) + 1):
if line.startswith("on"):
ps.add((x, y, z))
elif line.startswith("off"):
if (x, y, z) in ps:
ps.remove((x, y, z))
else:
assert False
print(len(ps))
# For part 1, we were limited to an area of -50..50 for all three directions.
# For part 2, this limitation is no longer in place. That means we cannot keep
# track of all the points individually. Instead, it will be necessary. To keep
# track of regions. Ultimately, we then have to perform arithmetic on the
# regions. This is not trivial enough for me right now.

143
2021/d23.py Normal file
View File

@ -0,0 +1,143 @@
import heapq
from lib import get_data
from lib import Grid2D
from collections import deque
from collections import defaultdict
CHAR_COSTS = {"A": 1, "B": 10, "C": 100, "D": 1000}
HALL_SPOTS = set([(1, x) for x in [1, 2, 4, 6, 8, 10, 11]])
for n_amphs in [2, 4]:
AMPH_SPOTS = {}
for i, c in enumerate("ABCD"):
spots = [(r, 3 + i * 2) for r in range(2, 2 + n_amphs)]
AMPH_SPOTS[c] = tuple(spots)
data = get_data(__file__)
lines = data.splitlines()
if n_amphs == 4:
lines.insert(-2, " #D#C#B#A# ")
lines.insert(-2, " #D#B#A#C# ")
lines[-2] = lines[-2] + " "
lines[-1] = lines[-1] + " "
data = "\n".join(lines)
g = Grid2D(data)
GRID_EMPTY = g.clone()
def mdist(p1, p2):
return abs(p1[0] - p2[0]) + abs(p1[1] - p2[1])
poss = []
for c in "ABCD":
for coord in g.find(c):
poss.append((coord[0], coord[1], c, False))
GRID_EMPTY[coord] = "."
poss = tuple(poss)
def dist(n1, n2):
"""cost from node to node"""
if n1 == 0:
return 0
return n2[1] - n1[1]
def h(node):
"""heuristic function (never overestimate)"""
cost = 0
for r, c, char, _ in node[0]:
if (r, c) not in AMPH_SPOTS[char]:
cost += mdist((r, c), AMPH_SPOTS[char][0]) * CHAR_COSTS[char]
return cost
def is_goal(node):
for r, c, char, _ in node[0]:
if (r, c) in AMPH_SPOTS[char]:
continue
return False
return True
def find_accessible_spots(pos: tuple[int, int], char, blocked):
start = (pos, 0)
visited = set()
queue = deque([start])
cost = CHAR_COSTS[char]
options = []
while queue:
pos, dist = queue.popleft()
if pos in visited:
continue
visited.add(pos)
for neighbor in GRID_EMPTY.neighbors_ort(pos):
if neighbor in blocked:
continue
if GRID_EMPTY[neighbor] != ".":
continue
if neighbor not in visited:
new_state = (neighbor, dist + cost)
queue.append(new_state)
options.append(new_state)
return options
def neighbors(node):
nbs = []
blocked = []
blocked_to_char = defaultdict(str)
for r, c, char, _ in node[0]:
blocked.append((r, c))
blocked_to_char[(r, c)] = char
for i, (r, c, char, moved) in enumerate(node[0]):
if moved and (r, c) in AMPH_SPOTS[char]:
continue
for pos, dist in find_accessible_spots((r, c), char, blocked):
# cannot move to hall spots twice
if pos in HALL_SPOTS and moved:
continue
# can only move to hall spot or amph spot (of the right type)
if not (pos in HALL_SPOTS or pos in AMPH_SPOTS[char]):
continue
# only move into amph spot if all lower spots are already occupied by the correct amph
if pos in AMPH_SPOTS[char] and not all(
blocked_to_char[AMPH_SPOTS[char][i]] == char
for i in range(
AMPH_SPOTS[char].index(pos) + 1, len(AMPH_SPOTS[char])
)
):
continue
amphs = list(node[0])
amphs[i] = (pos[0], pos[1], char, True)
neighbor = (tuple(amphs), node[1] + dist)
nbs.append(neighbor)
return nbs
starts = [(poss, 0)]
open_set = []
g_score = {}
cost = None
for start in starts:
heapq.heappush(open_set, (h(start), start))
g_score[start] = dist(0, start)
while open_set:
current_f_score, current = heapq.heappop(open_set)
if is_goal(current):
assert current_f_score == g_score[current]
cost = g_score[current]
break
for neighbor in neighbors(current):
tentative_g_score = g_score[current] + dist(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))
print(cost)

63
2021/d24.py Normal file
View File

@ -0,0 +1,63 @@
from lib import get_data
data = get_data(__file__)
# lines = data.splitlines()
# blocks = [lines[i:i + 18] for i in range(0, len(lines), 18)]
# blocks = zip(*blocks)
# for row in blocks:
# print("; ".join(set(row)))
def run(number=None):
inputs = []
if number is not None:
for c in str(number):
if c == "0":
return
inputs.append(int(c))
inputs = list(reversed(inputs))
else:
inputs = None
regs = {c: 0 for c in "wxyz"}
for i, line in enumerate(data.splitlines()):
if line.startswith("inp"):
if inputs is not None:
if inputs == []:
print(number, regs)
return
else:
op, a = line.split()
regs[a] = inputs.pop()
else:
# print(regs)
op, a = line.split()
regs[a] = int(input(f"{i // 14} > "))
continue
op, a, b = line.split()
b = regs[b] if b in regs else int(b)
match op:
case "add":
regs[a] = regs[a] + b
case "mul":
regs[a] = regs[a] * b
case "div":
regs[a] = regs[a] // b
case "mod":
regs[a] = regs[a] % b
case "eql":
regs[a] = 1 if regs[a] == b else 0
print("end", inputs, number, regs)
run(19111111111111)
# while True:
# try:
# run()
# except KeyboardInterrupt:
# print("\n restart <")
# pass

29
2021/d25.py Normal file
View File

@ -0,0 +1,29 @@
from lib import get_data
from lib import Grid2D
data = get_data(__file__)
g = Grid2D(data)
seen = set()
for i in range(10**9):
if g.hash() in seen:
print(i)
break
seen.add(g.hash())
blocked = set(g.find(">v"))
for p in g.find(">"):
np = (p[0], (p[1] + 1) % g.n_cols)
if np in blocked:
continue
g[p] = "."
g[np] = ">"
blocked = set(g.find(">v"))
for p in g.find("v"):
np = ((p[0] + 1) % g.n_rows , p[1])
if np in blocked:
continue
g[p] = "."
g[np] = "v"

View File

@ -172,24 +172,31 @@ Solutions and utility script for Advent of Code challenges in Python.
## AoC 2021
- Day 1: 4:01 (Haha. Why am I so bad?!?!)
- Day 2: 6:36 (Okay. I might as well stop doing these.)
- Day 3: 23:46 (How long can I take when I try to take long?)
- Day 4: 22:18 (No way. Such stupid bugs.)
- Day 5: 13:30 (Decent, but obviously too slow.)
- Day 6: 16:33 (Right idea quickly but then struggled to implement.)
- Day 7: 6:12 (Decent, but too slow again, obviously. Maybe fast enough for part 1.)
- Day 8: 72:00 (Yeah, that was too much of a struggle.)
- Day 9: 8:07 (37th okay, okay)
- Day 10: 15:50 (I was pretty happy with that but double the 100th place.)
- Day 11: 11:43 (Could have been much faster here.)
- Day 12: 19:11 (Okayish, but of course too slow for leaderboard.)
- Day 13: 16:48 (Should have been way faster.)
- Day 14: 25:52 (Not hard but just too slow.)
- Day 15: 19:17 (Not that bad. Multiplying the thing threw me off.)
- Day 16: 50:01 (Way too slow. Was non-trivial but fun. Much better was feasible.)
- Day 17: 21:59 (Not tricky again but struggling for no reason.)
- Day 18:
- Day 1: 4:01 (Haha. Why am I so bad?!?!)
- Day 2: 6:36 (Okay. I might as well stop doing these.)
- Day 3: 23:46 (How long can I take when I try to take long?)
- Day 4: 22:18 (No way. Such stupid bugs.)
- Day 5: 13:30 (Decent, but obviously too slow.)
- Day 6: 16:33 (Right idea quickly but then struggled to implement.)
- Day 7: 6:12 (Decent, but too slow again, obviously. Maybe fast enough for part 1.)
- Day 8: 72:00 (Yeah, that was too much of a struggle.)
- Day 9: 8:07 (37th okay, okay)
- Day 10: 15:50 (I was pretty happy with that but double the 100th place.)
- Day 11: 11:43 (Could have been much faster here.)
- Day 12: 19:11 (Okayish, but of course too slow for leaderboard.)
- Day 13: 16:48 (Should have been way faster.)
- Day 14: 25:52 (Not hard but just too slow.)
- Day 15: 19:17 (Not that bad. Multiplying the thing threw me off.)
- Day 16: 50:01 (Way too slow. Was non-trivial but fun. Much better was feasible.)
- Day 17: 21:59 (Not tricky again but struggling for no reason.)
- Day 18: 162:00 (I couldn't figure out how to solve it as a tree so I did the basic way.)
- Day 19:
- Day 20: 105:00 (That wasn't easy but was able to solve in one go.)
- Day 21: 37:45 (Wasn't hard but I was just too slow.)
- Day 22:
- Day 23: 81:38 (Fun but obviously not competitive.)
- Day 24:
- Day 25: 15:43 (That could have been much much faster.)
## AoC 2022

4
lib.py
View File

@ -59,6 +59,10 @@ class Grid2D:
def hash(self):
return tuple(map(lambda row: tuple(row), self.grid))
def clone(self):
from copy import deepcopy
return deepcopy(self)
def clone_with_val(self, val):
c = Grid2D("d\nd")
c.n_rows = self.n_rows