Compare commits
2 Commits
ff3d33a614
...
a2a6ca52d1
Author | SHA1 | Date | |
---|---|---|---|
a2a6ca52d1 | |||
4acc229c5a |
115
2021/d18.py
Normal file
115
2021/d18.py
Normal 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
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"
|
||||
|
43
README.md
43
README.md
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user