Solve 2021 day 20, 21, 23, 25
This commit is contained in:
51
2021/d20.py
Normal file
51
2021/d20.py
Normal 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
65
2021/d21.py
Normal 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
27
2021/d22.py
Normal 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
143
2021/d23.py
Normal 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
63
2021/d24.py
Normal 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
29
2021/d25.py
Normal 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"
|
||||||
|
|
||||||
@@ -191,7 +191,12 @@ Solutions and utility script for Advent of Code challenges in Python.
|
|||||||
- Day 17: 21:59 (Not tricky again but struggling for no reason.)
|
- 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 18: 162:00 (I couldn't figure out how to solve it as a tree so I did the basic way.)
|
||||||
- Day 19:
|
- Day 19:
|
||||||
- Day 20:
|
- 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
|
## AoC 2022
|
||||||
|
|
||||||
|
|||||||
4
lib.py
4
lib.py
@@ -59,6 +59,10 @@ class Grid2D:
|
|||||||
def hash(self):
|
def hash(self):
|
||||||
return tuple(map(lambda row: tuple(row), self.grid))
|
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):
|
def clone_with_val(self, val):
|
||||||
c = Grid2D("d\nd")
|
c = Grid2D("d\nd")
|
||||||
c.n_rows = self.n_rows
|
c.n_rows = self.n_rows
|
||||||
|
|||||||
Reference in New Issue
Block a user