Compare commits

...

57 Commits

Author SHA1 Message Date
288a0809ff Solve 2025 day 7 2026-02-16 12:23:56 -05:00
42e0c68d93 Implement 2025 day 6 2026-02-16 11:51:21 -05:00
867a7ed2df Solve 2025 day 5 2026-01-11 12:12:55 -05:00
df0682e989 Solve 2025 day 3 and 4 2026-01-11 10:33:21 -05:00
e5421854a9 Do day 3 and 4 for 2025 2025-12-26 20:18:44 -05:00
183f909508 Solve 2025 day 1 and 2 2025-12-14 10:36:32 -05:00
e655580b3f Setup for 2025 2025-12-13 20:32:11 -05:00
c9145e6106 Rename 2024 scripts for nicer ordering 2025-12-13 20:19:48 -05:00
7013c5b0a0 Solve 2024 day 21 to finish the year 2024-12-30 23:11:54 -05:00
376a2eac09 2024 d21 wip 2024-12-30 22:28:06 -05:00
2ee16eb1ba Solve 2024 day 24 2024-12-25 12:14:04 -05:00
8cc199105c Solve 2024 day 25 2024-12-25 10:42:33 -05:00
3dd208c088 Solve 2024 day 23 2024-12-24 11:33:35 -05:00
265829715a Solve 2024 day 22 2024-12-22 00:31:27 -05:00
3979a60fa4 2024 d21 wip that is not going anywhere 2024-12-21 22:37:40 -05:00
acdf82d768 2024 d21 part 1 2024-12-21 01:24:24 -05:00
f4edfaa680 Solve 2024 day 20 2024-12-20 01:54:25 -05:00
299f1cf2ff Solve 2024 day 19 2024-12-19 00:22:44 -05:00
744dbb4ffc Solve 2024 till day 18 2024-12-18 22:09:38 -05:00
4ad7075785 Solve day 2024 day 14 2024-12-14 00:40:22 -05:00
3432d81941 Solve 2024 day 13 2024-12-13 00:58:31 -05:00
efb1e3e551 Solve 2024 day 12 2024-12-12 23:57:04 -05:00
022b95eb97 Solve 2024 day 11 2024-12-11 00:38:50 -05:00
96f15d07fc Make day 10 2024 nice 2024-12-10 20:01:11 -05:00
83fbf59bd7 Solve 2024 day 10 2024-12-10 00:24:03 -05:00
e9211c26a3 Implement alternative solution for d9 2024-12-09 22:13:25 -05:00
2a10543852 Solve 2024 day 9 2024-12-09 01:11:22 -05:00
b747d3973a Finish 2021 thanks so much Eric 2024-12-08 21:24:20 -05:00
b622ca92b9 Solve 2024 day 8 2024-12-08 00:28:54 -05:00
e9a4ce6414 Solve 2024 day 7 2024-12-07 00:18:28 -05:00
36f8f709e1 Make day 6 a little nicer 2024-12-06 00:28:42 -05:00
fae61ae6db Solve 2024 day 6 2024-12-06 00:18:48 -05:00
63166ddce8 Solve 2024 day 5 2024-12-05 00:28:21 -05:00
24174b8cb9 Solve 2024 day 4 2024-12-04 00:29:03 -05:00
0fb75687d1 Improve 2024 day 3 regex 2024-12-03 00:52:24 -05:00
b627e97c5a Solve 2024 day 3 2024-12-03 00:15:35 -05:00
893a8e3dbb Solve 2024 day 2 2024-12-02 00:09:58 -05:00
12662b912c Solve 2024 day 1 2024-12-01 00:10:28 -05:00
54bbc228f4 Solve 2021 day 22 2024-11-30 12:22:44 -05:00
2d3a44760e Solve 2021 day 24 2024-11-29 16:46:01 -05:00
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
ff3d33a614 Solve 2021 day 17 2024-11-24 09:53:34 -05:00
bde0a5d622 Solve 2021 day 16 2024-11-23 14:54:02 -05:00
9ac4782157 Solve 2021 day 15 2024-11-23 14:00:08 -05:00
43767f7b62 Solve 2021 day 12-14 2024-11-23 09:36:43 -05:00
e9bd759dba Solve 2021 day 7-11 2024-11-21 08:43:15 -05:00
11bcbce099 Solve 2021 day 6 2024-11-17 06:11:54 -05:00
074a7c4458 Solve 2021 day 4 and 5 2024-11-17 05:45:47 -05:00
10e1e567c8 Start with 2021 2024-10-20 20:48:27 -04:00
e73fa3bae7 Update 2015 solutions 2024-10-20 15:19:25 -04:00
87ab42743e Finish 2020. 2024-10-13 16:28:30 -04:00
800d1a6af3 Finish 2019. 2024-10-13 10:38:21 -04:00
5dc7c4392f Solve 2019 day 24 2024-10-13 08:54:39 -04:00
67914a642a Solve 2019 day 23 2024-10-11 22:01:40 -04:00
df31b47934 Solve 2019 day 22 2024-10-11 21:21:26 -04:00
3991842ef0 Solve 2018 day 24 and 25 2024-10-11 18:33:04 -04:00
87 changed files with 4604 additions and 782 deletions

2
.gitignore vendored
View File

@@ -4,6 +4,8 @@
__pycache__/
*.py[cod]
*$py.class
.python-version
uv.lock
# C extensions
*.so

View File

@@ -1,8 +1,9 @@
from lib import *
from lib import get_data
data = open(0).read()
data = get_data(__file__)
part_2 = True
t = sum((1 if c == "(" else -1 for c in data))
print(t)
floor = 0
for i, c in enumerate(data):
@@ -10,13 +11,6 @@ for i, c in enumerate(data):
floor += 1
elif c == ")":
floor -= 1
else:
print(c)
assert False
if part_2 and floor == -1:
if floor == -1:
print(i + 1)
break
if not part_2:
print(floor)

View File

@@ -1,35 +1,29 @@
from functools import lru_cache
data = open(0).read().strip()
from lib import get_data
part_1 = False
if part_1:
repeats = 40
else:
repeats = 50
data = get_data(__file__)
@lru_cache
def look_and_say(data: str) -> str:
r = ""
i = 0
while i < len(data):
count = 0
c = data[i]
while i < len(data) and data[i] == c:
def iter(xs):
if not xs:
return []
grouped = []
current_element = xs[0]
count = 1
for element in xs[1:]:
if element == current_element:
count += 1
i += 1
r += f"{count}{c}"
return r
else:
grouped.append(count)
grouped.append(current_element)
current_element = element
count = 1
grouped.append(count)
grouped.append(current_element)
return grouped
CHUNK_SIZE = 10000
for _ in range(repeats):
ndata = ""
lo, up = 0, CHUNK_SIZE
while up < len(data):
while up < len(data) and data[up - 1] == data[up]:
up += 1
ndata += look_and_say(data[lo:up])
lo, up = up, up + CHUNK_SIZE
ndata += look_and_say(data[lo:up])
data = ndata
print(len(data))
for repeat in [40, 50]:
xs = list(map(int, list(data.strip())))
for _ in range(repeat):
xs = iter(xs)
print(len(xs))

View File

@@ -1,4 +1,7 @@
data = open(0).read().strip()
from lib import get_data
data = get_data(__file__).strip()
def is_valid(pw):
# Passwords must include one increasing straight of at least three letters,
@@ -26,24 +29,26 @@ def is_valid(pw):
return False
return True
def inc(pw):
pw = list(map(ord, pw))
for i in range(len(pw) - 1, -1, -1):
pw[i] += 1
if pw[i] == ord('z') + 1:
pw[i] = ord('a')
if pw[i] == ord("z") + 1:
pw[i] = ord("a")
else:
break
return "".join(map(chr, pw))
part_1 = False
part_1 = True
valid_count = 0
while True:
data = inc(data)
if is_valid(data):
valid_count += 1
if part_1 and valid_count == 1:
break
elif valid_count == 2:
break
print(data)
if part_1 and valid_count == 1:
print(data)
elif valid_count == 2:
print(data)
break

View File

@@ -1,27 +1,37 @@
from lib import get_data
import json
data = open(0).read().strip()
part_2 = False
data = get_data(__file__).strip()
def addup(d):
r = 0
if isinstance(d, list):
for e in d:
v = addup(e)
if v is not None:
r += v
elif isinstance(d, dict):
for e in d.values():
v = addup(e)
if v is None:
return 0
r += v
elif isinstance(d, str):
if part_2 and d == "red":
return None
o = json.loads(data)
def xsum(obj, part_2=False):
t = 0
if type(obj) is int:
t += obj
elif type(obj) is list:
for o in obj:
t += xsum(o, part_2)
elif type(obj) is dict:
if part_2:
st = 0
for o in obj.values():
st += xsum(o, part_2)
if type(o) is str and o == "red":
break
else:
t += st
else:
for o in obj.values():
t += xsum(o, part_2)
elif type(obj) is str:
pass
else:
r += d
return r
print(obj)
assert False
return t
data = json.loads(data)
print(addup(data))
print(xsum(o))
print(xsum(o, True))

View File

@@ -1,35 +1,32 @@
from lib import get_data
from collections import defaultdict
from itertools import permutations
data = open(0).read().strip()
part_2 = False
data = get_data(__file__).strip()
pairs = defaultdict(int)
people = set()
scores = {}
for line in data.splitlines():
a, _, wl, score, _, _, _, _, _, _, b = line.split()
b = b.replace(".", "")
score = int(score)
if wl == "lose":
score = -score
a, _, gainlose, amount, _, _, _, _, _, _, b = line[:-1].split()
amount = int(amount)
if gainlose == "lose":
amount = -amount
pairs[(a, b)] = amount
people.add(a)
people.add(b)
scores[(a, b)] = score
if part_2:
scores[(a, "me")] = 0
scores[("me", a)] = 0
scores[(b, "me")] = 0
scores[("me", b)] = 0
if part_2:
people.add("me")
max_score = 0
for p in permutations(list(people)):
s = 0
for i in range(len(p)):
a, b = p[i], p[(i + 1) % len(p)]
s += scores[(a, b)]
s += scores[(b, a)]
max_score = max(max_score, s)
def calc_gain(people):
maxgain = 0
for xs in permutations(list(people)):
gain = 0
for i in range(len(xs)):
gain += pairs[(xs[i], xs[(i + 1) % len(xs)])]
gain += pairs[(xs[(i + 1) % len(xs)], xs[i])]
maxgain = max(maxgain, gain)
return maxgain
print(max_score)
print(calc_gain(people))
people.add("Felix")
print(calc_gain(people))

View File

@@ -1,53 +1,40 @@
import lib
data = open(0).read().strip()
from lib import get_data
from lib import ints, fst
from collections import defaultdict
part_1 = False
rds = []
max_dist = 0
gtime = 2503
rds = []
data = get_data(__file__).strip()
deers = []
for line in data.splitlines():
speed, time, rest = lib.str_to_ints(line)
rds.append([speed, time, rest, 0, 0, 0])
speed *= 1000
speed, time, rest = ints(line)
deers.append([0, speed, time, rest, time, 0])
dist = 0
ctime = 0
while ctime < gtime:
t = min(time, gtime - ctime)
dist += (speed * t)
ctime += time
ctime += rest
max_dist = max(dist, max_dist)
points = defaultdict(int)
for _ in range(2503):
max_dists = defaultdict(list)
for i, deer in enumerate(deers):
dist, speed, time, rest, time_left, rest_left = deer
if time_left > 0:
time_left -= 1
dist += speed
elif time_left == 0:
time_left = -1
rest_left = rest - 1
elif rest_left > 0:
rest_left -= 1
elif rest_left == 0:
time_left = time - 1
dist += speed
rest_left = -1
max_dists[dist].append(i)
if part_1:
print(max_dist // 1000)
deer[0] = dist
deer[4] = time_left
deer[5] = rest_left
scores = [0 for _ in range(len(rds))]
for _ in range(gtime):
for i in range(len(rds)):
speed, time, rest, dist, travel_time, rest_time = rds[i]
rd = rds[i]
if travel_time < time:
rd[3] += speed
rd[4] += 1
elif rest_time < rest:
rd[5] += 1
if rd[5] == rest:
rd[4] = 0
rd[5] = 0
max_deers = max(max_dists.items())
for di in max_deers[1]:
points[di] += 1
max_i = -1
max_v = 0
for i, rd in enumerate(rds):
if rd[3] > max_v:
max_i = i
max_v = rd[3]
for i, rd in enumerate(rds):
if rd[3] == max_v:
scores[i] += 1
if not part_1:
print(max(scores))
print(max(map(fst, deers)))
print(max(points.values()))

View File

@@ -1,19 +1,17 @@
from lib import *
from lib import get_data, ints
data = open(0).readlines()
part_1 = True
if part_1:
a = 0
for line in data:
l, w, h = list(map(int, line.split("x")))
slack = min([l * w, w * h, h * l])
a += (2*l*w + 2*w*h + 2*h*l + slack)
else:
a = 0
for line in data:
l, w, h = list(map(int, line.split("x")))
sd = min([2 * (l + w), 2 * (w + h), 2 * (h + l)])
a += sd + (l * w * h)
data = get_data(__file__)
a = 0
for line in data.splitlines():
l, w, h = ints(line)
slack = min([l * w, w * h, h * l])
a += 2 * l * w + 2 * w * h + 2 * h * l + slack
print(a)
a = 0
for line in data.splitlines():
l, w, h = ints(line)
sd = min([2 * (l + w), 2 * (w + h), 2 * (h + l)])
a += sd + (l * w * h)
print(a)

View File

@@ -4,6 +4,7 @@ import lib
data = int(open(0).read().strip())
part_1 = False
def calc(n):
fs = lib.prime_factors(n)
vs = set([1, n])
@@ -21,6 +22,7 @@ def calc(n):
r += e * 11
return r
for i in range(3, 10000000):
c = calc(i)
if c >= data:

View File

@@ -1,9 +1,7 @@
import sys
from lib import *
from lib import get_data
from lib import V
data = open(0).read()
part_1 = False
data = get_data(__file__)
DIRS = {
"^": (-1, 0),
@@ -12,30 +10,27 @@ DIRS = {
"<": (0, -1),
}
if part_1:
pos = (0, 0)
poss = set([pos])
pos = V(0, 0)
poss = set([pos])
for c in data:
d = DIRS[c]
pos = pos[0] + d[0], pos[1] + d[1]
poss.add(pos)
for c in data:
d = DIRS[c]
pos = pos + d
poss.add(pos)
print(len(poss))
sys.exit(0)
print(len(poss))
a = (0, 0)
b = (0, 0)
a = V(0, 0)
b = V(0, 0)
poss = set([a, b])
for i, c in enumerate(data):
if i % 2 == 0:
d = DIRS[c]
a = a[0] + d[0], a[1] + d[1]
a = a + d
poss.add(a)
else:
d = DIRS[c]
b = b[0] + d[0], b[1] + d[1]
b = b + d
poss.add(b)
print(len(poss))
sys.exit(0)

View File

@@ -1,22 +1,15 @@
from lib import *
from lib import get_data
import hashlib
part_1 = True
data = get_data(__file__).strip()
if part_1:
digits = 5
else:
digits = 6
data = open(0).read().strip()
for i in range(10**9):
text = data + str(i)
md5_hash = hashlib.md5(text.encode()).hexdigest()
for c in md5_hash[:digits]:
if c != "0":
for digits in [5, 6]:
for i in range(10**9):
text = data + str(i)
md5_hash = hashlib.md5(text.encode()).hexdigest()
for c in md5_hash[:digits]:
if c != "0":
break
else:
print(i)
break
else:
print(i)
break

View File

@@ -1,53 +1,56 @@
from lib import *
from lib import get_data
data = open(0).read()
data = get_data(__file__)
part_1 = True
if part_1:
res = 0
for line in data.splitlines():
vc = 0
for c in line:
if c in "aoeui":
vc += 1
if vc < 3:
continue
prev = None
for c in line:
if c == prev:
break
prev = c
else:
continue
def is_nice(line):
# It contains at least three vowels (aeiou only), like aei, xazegov, or aeiouaeiouaeiou.
vc = 0
for v in "aeiou":
vc += line.count(v)
if vc < 3:
return False
contains = False
ss = ["ab", "cd", "pq", "xy"]
for s in ss:
if s in line:
contains = True
# It contains at least one letter that appears twice in a row, like xx,
# abcdde (dd), or aabbccdd (aa, bb, cc, or dd).
for i in range(len(line) - 1):
if line[i] == line[i + 1]:
break
else:
return False
if contains:
continue
res += 1
print(res)
else:
res = 0
for line in data.splitlines():
pairs = {}
for i in range(0, len(line) - 1):
p = line[i:i+2]
if p in pairs and i > pairs[p] + 1:
break
if not p in pairs:
pairs[p] = i
else:
continue
# It does not contain the strings ab, cd, pq, or xy, even if they are
# part of one of the other requirements.
for i in range(len(line) - 1):
cc = "".join(line[i : i + 2])
if cc in ["ab", "cd", "pq", "xy"]:
return False
return True
for i in range(0, len(line) - 2):
if line[i] == line[i + 2]:
break
else:
continue
res += 1
print(res)
def is_nice_2(line):
good = False
for i in range(len(line) - 1):
cc = "".join(line[i : i + 2])
for j in range(i + 2, len(line) - 1):
dd = "".join(line[j : j + 2])
if cc == dd:
good = True
if not good:
return False
for i in range(len(line) - 2):
cc = "".join(line[i : i + 3])
if cc[0] == cc[2]:
break
else:
return False
return True
t = sum(1 if is_nice(line) else 0 for line in data.splitlines())
print(t)
t = sum(1 if is_nice_2(line) else 0 for line in data.splitlines())
print(t)

View File

@@ -1,33 +1,29 @@
from lib import *
from lib import get_data, ints
from collections import defaultdict as DD
data = open(0).read()
part_2 = True
lights = [[0 for _ in range(1000)] for _ in range(1000)]
data = get_data(__file__)
ons = set()
ons2 = DD(int)
for line in data.splitlines():
x1, y1, x2, y2 = str_to_ints(line)
x1, y1, x2, y2 = ints(line)
for x in range(x1, x2 + 1):
for y in range(y1, y2 + 1):
if part_2:
if "on" in line:
lights[x][y] += 1
elif "off" in line:
lights[x][y] = max(0, lights[x][y] - 1)
if "off" in line:
if (x, y) in ons:
ons.remove((x, y))
ons2[(x, y)] = max(ons2[(x, y)] - 1, 0)
elif "on" in line:
ons.add((x, y))
ons2[(x, y)] += 1
elif "toggle" in line:
if (x, y) in ons:
ons.remove((x, y))
else:
lights[x][y] += 2
ons.add((x, y))
ons2[(x, y)] += 2
else:
if "on" in line:
lights[x][y] = 1
elif "off" in line:
lights[x][y] = 0
else:
if lights[x][y] == 1:
lights[x][y] = 0
else:
lights[x][y] = 1
assert False
res = 0
for row in lights:
res += sum(row)
print(res)
print(len(ons))
print(sum(ons2.values()))

View File

@@ -1,90 +1,54 @@
from lib import *
from lib import get_data
data = open(0).read()
data = get_data(__file__)
part_2 = False
if part_2:
gates = {"b": 16076}
else:
gates = {}
while "a" not in gates:
for line in data.splitlines():
lhs, rhs = line.split(" -> ")
if part_2 and rhs == "b":
continue
lhs = lhs.strip()
if "NOT" in lhs:
op, op1 = lhs.split(" ")
assert op == "NOT"
try:
op1 = int(op1)
gates[rhs] = ~op1 & 0xffff
except ValueError:
if op1 in gates and isinstance(gates[op1], int):
gates[rhs] = ~gates[op1] & 0xffff
elif "OR" in lhs:
op1, op, op2 = lhs.split(" ")
assert op == "OR"
try:
op1 = int(op1)
except ValueError:
if op1 in gates and isinstance(gates[op1], int):
op1 = gates[op1]
try:
op2 = int(op2)
except ValueError:
if op2 in gates and isinstance(gates[op2], int):
op2 = gates[op2]
if not (isinstance(op1, int) and isinstance(op2, int)):
continue
gates[rhs] = (op1 | op2)
elif "AND" in lhs:
op1, op, op2 = lhs.split(" ")
assert op == "AND"
try:
op1 = int(op1)
except ValueError:
if op1 in gates and isinstance(gates[op1], int):
op1 = gates[op1]
try:
op2 = int(op2)
except ValueError:
if op2 in gates and isinstance(gates[op2], int):
op2 = gates[op2]
if not (isinstance(op1, int) and isinstance(op2, int)):
continue
gates[rhs] = (op1 & op2)
elif "LSHIFT" in lhs:
op1, op, op2 = lhs.split(" ")
op2 = int(op2)
try:
op1 = int(op1)
except ValueError:
if op1 in gates:
op1 = gates[op1]
else:
continue
gates[rhs] = (op1 << op2) & 0xffff
elif "RSHIFT" in lhs:
op1, op, op2 = lhs.split(" ")
op2 = int(op2)
try:
op1 = int(op1)
except ValueError:
if op1 in gates:
op1 = gates[op1]
else:
continue
gates[rhs] = (op1 >> op2) & 0xffff
def run(wires={}):
def get(a):
try:
return int(a)
except ValueError:
pass
if a in wires:
return wires[a]
else:
try:
lhs = int(lhs)
gates[rhs] = lhs
except ValueError:
if lhs in gates:
gates[rhs] = gates[lhs]
return None
print(gates["a"])
while "a" not in wires:
for line in data.splitlines():
lhs, rhs = line.split(" -> ")
if rhs in wires:
continue
match lhs.split():
case [a, "AND", b]:
a, b = get(a), get(b)
if a is not None and b is not None:
wires[rhs] = a & b
case [a, "OR", b]:
a, b = get(a), get(b)
if a is not None and b is not None:
wires[rhs] = a | b
case [a, "LSHIFT", b]:
a, b = get(a), get(b)
if a is not None and b is not None:
wires[rhs] = a << b
case [a, "RSHIFT", b]:
a, b = get(a), get(b)
if a is not None and b is not None:
wires[rhs] = a >> b
case ["NOT", a]:
a = get(a)
if a is not None:
wires[rhs] = ~a & 0xFFFF
case [a]:
a = get(a)
if a is not None:
wires[rhs] = a
return wires
a = run()["a"]
print(a)
a2 = run(wires={"b": a})["a"]
print(a2)

View File

@@ -1,26 +1,25 @@
from lib import *
from lib import get_data
import re
data = open(0).read()
part_1 = False
data = get_data(__file__)
if part_1:
r1 = re.compile(r"\\x[0-9a-f][0-9a-f]")
r2 = re.compile(r"\\\\")
r3 = re.compile(r"\\\"")
r1 = re.compile(r"\\x[0-9a-f][0-9a-f]")
r2 = re.compile(r"\\\\")
r3 = re.compile(r"\\\"")
enc, mem = 0, 0
for line in data.splitlines():
mem += len(line)
line = r1.sub("^", line)
line = r2.sub("^", line)
line = r3.sub("^", line)
enc += len(line) - 2
print(mem - enc)
else:
ori, enc = 0, 0
for line in data.splitlines():
ori += len(line)
line = line.replace("\\", "\\\\")
line = line.replace("\"", "\\\"")
enc += len(line) + 2
print(enc - ori)
enc, mem = 0, 0
for line in data.splitlines():
mem += len(line)
line = r1.sub("^", line)
line = r2.sub("^", line)
line = r3.sub("^", line)
enc += len(line) - 2
print(mem - enc)
ori, enc = 0, 0
for line in data.splitlines():
ori += len(line)
line = line.replace("\\", "\\\\")
line = line.replace('"', '\\"')
enc += len(line) + 2
print(enc - ori)

View File

@@ -1,42 +1,27 @@
from lib import *
from typing import Iterator
from lib import get_data
from itertools import permutations
# Just for fun instead of using itertools.permutations.
def permutations(xs) -> Iterator:
assert len(xs) > 0
if len(xs) == 1:
yield xs
else:
x = xs.pop()
for p in permutations(xs):
for i in range(len(p) + 1):
pn = list(p)
pn.insert(i, x)
yield pn
part_1 = False
data = open(0).read()
data = get_data(__file__)
dists = {}
nodes = set()
for line in data.splitlines():
path, dist = line.split(" = ")
dist = int(dist)
a, b = path.split(" to ")
a, _, b, _, d = line.split()
nodes.add(a)
nodes.add(b)
dist = int(d)
dists[(a, b)] = dist
dists[(b, a)] = dist
if part_1:
mdist = 10**12
for route in permutations(nodes):
dist = sum([dists[(route[i], route[i + 1])] for i in range(len(route) - 1)])
mdist = min(dist, mdist)
print(mdist)
else:
mdist = 0
for route in permutations(nodes):
dist = sum([dists[(route[i], route[i + 1])] for i in range(len(route) - 1)])
mdist = max(dist, mdist)
print(mdist)
d_min = 10**12
d_max = 0
for p in permutations(list(nodes)):
d = 0
for i in range(len(p) - 1):
a, b = p[i], p[i + 1]
d += dists[(a, b)]
d_min = min(d, d_min)
d_max = max(d, d_max)
print(d_min)
print(d_max)

View File

@@ -1,11 +1,6 @@
from lib import get_data, add2
from collections import defaultdict
data = "^WNE$"
data = "^ENWWW(NEEE|SSE(EE|N))$"
data = "^ESSWWN(E|NNENN(EESS(WNSE|)SSS|WWWSSSSE(SW|NNNE)))$"
data = "^WSSEESWWWNW(S|NENNEEEENN(ESSSSW(NWSW|SSEN)|WSWWN(E|WWS(E|SS))))$"
DIRS = {
"N": (-1, 0),
"E": (0, 1),
@@ -13,148 +8,87 @@ DIRS = {
"W": (0, -1),
}
data = get_data(__file__).strip()
g = defaultdict(set)
seen = set()
stack: list[tuple[tuple, int, list]] = [((0, 0), 0, [])]
while len(stack) > 0:
pos, i, i_outs = stack.pop()
def part_1(data):
g = defaultdict(set)
c = (pos, i, tuple(i_outs))
if c in seen:
continue
else:
seen.add(c)
stack: list[tuple[tuple, int, list]] = [((0, 0), 0, [])]
while len(stack) > 0:
pos, i, i_outs = stack.pop()
assert i is not None
while i < len(data):
c = data[i]
if c in DIRS.keys():
npos = add2(pos, DIRS[c])
g[pos].add(npos)
g[npos].add(pos)
pos = npos
i += 1
elif c == "(":
to_continue = [i + 1]
open_count = 0
j_out = None
for j in range(i + 1, len(data)):
c = data[j]
if c == "|" and open_count == 0:
to_continue.append(j + 1)
elif c == "(":
open_count += 1
elif c == ")" and open_count != 0:
open_count -= 1
elif c == ")" and open_count == 0:
j_out = j
break
assert j_out is not None
assert i is not None
while i < len(data):
c = data[i]
if c in DIRS.keys():
npos = add2(pos, DIRS[c])
g[pos].add(npos)
g[npos].add(pos)
pos = npos
i += 1
elif c == "(":
to_continue = [i + 1]
open_count = 0
j_out = None
for j in range(i + 1, len(data)):
c = data[j]
if c == "|" and open_count == 0:
to_continue.append(j + 1)
elif c == "(":
open_count += 1
elif c == ")" and open_count != 0:
open_count -= 1
elif c == ")" and open_count == 0:
j_out = j
break
assert j_out is not None
for new_i in to_continue:
new_i_outs = list(i_outs)
new_i_outs.append(j_out)
stack.append((pos, new_i, new_i_outs))
break
elif c == "$":
break
elif c == ")" and len(i_outs) == 0:
assert False, "Encountered | without i_out"
elif c == ")":
i_new = i_outs.pop()
assert i == i_new
i += 1
elif c == "^":
i += 1
elif c == "|" and len(i_outs) == 0:
assert False, "Encountered | without i_out"
elif c == "|":
i = i_outs.pop()
i += 1
else:
assert False
for new_i in to_continue:
new_i_outs = list(i_outs)
new_i_outs.append(j_out)
stack.append((pos, new_i, new_i_outs))
break
elif c == "$":
break
elif c == ")" and len(i_outs) == 0:
assert False, "Encountered | without i_out"
elif c == ")":
i_new = i_outs.pop()
assert i == i_new
i += 1
elif c == "^":
i += 1
elif c == "|" and len(i_outs) == 0:
assert False, "Encountered | without i_out"
elif c == "|":
i = i_outs.pop()
i += 1
else:
assert False
seen = set()
dists = {}
xs = [(0, 0)]
steps = 0
over_thousand = set()
while len(xs) > 0:
nxs = []
for x in xs:
if x in seen:
continue
if steps >= 1000:
over_thousand.add(x)
seen.add(x)
dists[x] = steps
for nb in g[x]:
if not nb in seen:
nxs.append(nb)
xs = nxs
steps += 1
# def parse(i):
# max_len = 0
# cur_len = 0
# while i < len(data):
# c = data[i]
# if c in DIRS.keys():
# i += 1
# cur_len += 1
# elif c == "(":
# sub_len, i = parse(i + 1)
# cur_len += sub_len
# elif c == "|":
# max_len = max(max_len, cur_len)
# cur_len = 0
# i += 1
# elif c == ")":
# all_max = max(max_len, cur_len)
# return all_max, i + 1
# elif c == "$":
# max_len = max(max_len, cur_len)
# i += 1
# else:
# print(c)
# assert False
# return max_len, i
#
# print(parse(0)[0])
# g = defaultdict(set)
# def parse(xs, i):
# xs_orig = xs.copy()
# xs_done = []
#
# while i < len(data):
# c = data[i]
# if c in DIRS.keys():
# for xi in range(len(xs)):
# pos = xs[xi]
# npos = add2(pos, DIRS[c])
# g[pos].add(npos)
# g[npos].add(pos)
# xs[xi] = npos
# i += 1
# elif c == "(":
# xs, i = parse(xs, i + 1)
# elif c == "|":
# xs_done += xs
# xs = xs_orig
# i += 1
# elif c == ")":
# xs_done += xs
# return xs_done, i + 1
# elif c == "$":
# xs_done += xs
# i += 1
# else:
# assert False
# return xs_done, i
#
# parse([(0, 0)], 0)
seen = set()
dists = {}
xs = [(0, 0)]
steps = 0
while len(xs) > 0:
nxs = []
for x in xs:
if x in seen:
continue
seen.add(x)
dists[x] = steps
for nb in g[x]:
if not nb in seen:
nxs.append(nb)
xs = nxs
steps += 1
print(max(dists.values()))
def main():
# data = get_data(__file__).strip()
part_1(data)
if __name__ == "__main__":
main()
print(max(dists.values()))
print(len(over_thousand))

179
2018/d24.py Normal file
View File

@@ -0,0 +1,179 @@
import re
from lib import get_data, ints
from collections import defaultdict
from copy import deepcopy
r = re.compile(r"(immune|weak) to ([\w ,]+)")
r_strength = re.compile(r"(\w+) damage")
data = """Immune System:
17 units each with 5390 hit points (weak to radiation, bludgeoning) with an attack that does 4507 fire damage at initiative 2
989 units each with 1274 hit points (immune to fire; weak to bludgeoning, slashing) with an attack that does 25 slashing damage at initiative 3
Infection:
801 units each with 4706 hit points (weak to radiation) with an attack that does 116 bludgeoning damage at initiative 1
4485 units each with 2961 hit points (immune to radiation; weak to fire, cold) with an attack that does 12 slashing damage at initiative 4
"""
data = get_data(__file__)
ARMY, ID, UNITS, HITS, DAMG, INIT = 0, 1, 2, 3, 4, 5
groups = []
unit_meta = {}
id = 1
for a, p in enumerate(data.split("\n\n")):
for line in p.splitlines():
xs = ints(line)
if len(xs) == 0:
continue
assert len(xs) == 4
xs = [a, id] + xs
(attack,) = r_strength.findall(line)
defense = defaultdict(list)
for attr, values in r.findall(line):
defense[attr] = values.split(", ")
unit_meta[id] = attack, defense
groups.append(xs)
id += 1
def effective_power(group):
return (group[UNITS] * group[DAMG], group[INIT])
def damage(g1, g2):
"""Damage g1 does to g2"""
g1_id, g2_id = g1[ID], g2[ID]
g1_attack_type = unit_meta[g1_id][0]
g2_defenses = unit_meta[g2_id][1]
g2_immune = True if g1_attack_type in g2_defenses["immune"] else False
g2_weak = True if g1_attack_type in g2_defenses["weak"] else False
assert (g2_immune and g2_weak) != True
damage = g1[UNITS] * g1[DAMG]
if g2_immune:
damage = 0
elif g2_weak:
damage *= 2
return damage
groups_orig = deepcopy(groups)
while True:
groups = sorted(groups, key=effective_power, reverse=True)
attacks = {}
attacked = set()
for g in groups:
target_max_stats = 0, 0, 0
target = None
for t in groups:
if g[ARMY] == t[ARMY]:
continue
if g[ID] == t[ID]:
continue
if t[ID] in attacked:
continue
target_stats = damage(g, t), effective_power(t)[0], t[INIT]
if target_stats[0] == 0:
continue
if target_stats > target_max_stats:
target_max_stats = target_stats
target = t
if target is not None:
attacked.add(target[ID])
attacks[g[ID]] = target
groups = sorted(groups, key=lambda g: g[INIT], reverse=True)
for g in groups:
if g[ID] not in attacks:
continue
if g[UNITS] <= 0:
continue
target = attacks[g[ID]]
cdamage = damage(g, target)
units_dead = cdamage // target[HITS]
units_dead = units_dead if units_dead <= target[UNITS] else target[UNITS]
# print("immune" if g[ARMY] == 0 else "infection", g[ID], "attacks", target[ID], "dead", units_dead)
target[UNITS] -= units_dead
groups = [g for g in groups if g[UNITS] > 0]
armies = set()
for g in groups:
armies.add(g[ARMY])
assert len(armies) >= 1
if len(armies) == 1:
break
t = sum(g[UNITS] for g in groups)
print(t)
for boost in range(0, 1800):
groups = deepcopy(groups_orig)
for g in groups:
if g[ARMY] == 0:
g[DAMG] += boost
while True:
groups = sorted(groups, key=effective_power, reverse=True)
attacks = {}
attacked = set()
for g in groups:
target_max_stats = 0, 0, 0
target = None
for t in groups:
if g[ARMY] == t[ARMY]:
continue
if g[ID] == t[ID]:
continue
if t[ID] in attacked:
continue
target_stats = damage(g, t), effective_power(t)[0], t[INIT]
if target_stats[0] == 0:
continue
if target_stats > target_max_stats:
target_max_stats = target_stats
target = t
if target is not None:
attacked.add(target[ID])
attacks[g[ID]] = target
groups = sorted(groups, key=lambda g: g[INIT], reverse=True)
max_dead = 0
for g in groups:
if g[ID] not in attacks:
continue
if g[UNITS] <= 0:
continue
target = attacks[g[ID]]
cdamage = damage(g, target)
units_dead = cdamage // target[HITS]
units_dead = units_dead if units_dead <= target[UNITS] else target[UNITS]
max_dead = max(units_dead, max_dead)
# print("immune" if g[ARMY] == 0 else "infection", g[ID], "attacks", target[ID], "dead", units_dead)
target[UNITS] -= units_dead
if max_dead == 0:
break
groups = [g for g in groups if g[UNITS] > 0]
armies = set()
for g in groups:
armies.add(g[ARMY])
assert len(armies) >= 1
if len(armies) == 1:
army = groups[0][ARMY]
t = sum(g[UNITS] for g in groups)
if army == 0:
print(t)
exit(0)
break

38
2018/d25.py Normal file
View File

@@ -0,0 +1,38 @@
from lib import get_data, ints
def mdist(x, y):
return sum(abs(a - b) for a, b in zip(x, y))
data = get_data(__file__)
xss = [tuple(ints(line)) for line in data.splitlines()]
# check if we can use set (even though we didn't end up using sets)
assert len(xss) == len(set(xss))
unplaced = list(xss)
groups = [[unplaced.pop()]]
while unplaced:
# see if any start fits into the current group
current_group = groups[-1]
add = None
for u in unplaced:
for x in current_group:
if mdist(u, x) <= 3:
add = u
break
if add is not None:
break
if add is not None:
# if yes, add the start to that group and see if another one can be
# added to the same group
unplaced.remove(add)
current_group.append(add)
else:
# if no, create a new group with an unplaced start
groups.append([unplaced.pop()])
print(len(groups))

View File

@@ -4,16 +4,17 @@ from math import gcd
data = get_data(__file__)
deck = list(range(10007))
# part 1
for line in data.splitlines():
if "new stack" in line:
deck = list(reversed(deck))
elif "cut" in line:
n, = str_to_ints(line)
(n,) = str_to_ints(line)
deck = deck[n:] + deck[:n]
elif "increment" in line:
new_deck = [-1] * len(deck)
pos = 0
n, = str_to_ints(line)
(n,) = str_to_ints(line)
deck = list(reversed(deck))
while deck:
new_deck[pos] = deck.pop()
@@ -24,30 +25,94 @@ for line in data.splitlines():
print(deck.index(2019))
# idea: just follow the index of the target card
len = 119315717514047
len = 10007
index = 2019
orig_index = 2019
# figure out how to reverse...
index = orig_index
for line in data.splitlines():
if "new stack" in line:
new_index = len - (index + 1)
rev_index = -(new_index - len + 1)
assert rev_index == index
index = new_index
elif "cut" in line:
(cut,) = str_to_ints(line)
cut = (len + cut) % len
if index >= cut:
new_index = index - cut
else:
new_index = (len - cut) + index
rev_index = (new_index + cut) % len
assert rev_index == index
index = new_index
# calculate index from new_index and store in rev_index
elif "increment" in line:
(n,) = str_to_ints(line)
assert gcd(n, len) == 1
new_index = (n * index) % len
m = mod_inverse(n, len)
rev_index = (new_index * m) % len
assert rev_index == index
index = new_index
for i in range(101741582076661):
for line in data.splitlines():
if "new stack" in line:
index = len - (index + 1)
elif "cut" in line:
cut, = str_to_ints(line)
cut = (len + cut) % len
if index >= cut:
index = index - cut
else:
index = (len - cut) + index
elif "increment" in line:
n, = str_to_ints(line)
assert gcd(n, len) == 1
new_index = (n * index) % len
m = mod_inverse(n, len)
assert (new_index * m) % len == index
index = new_index
break
assert index == deck.index(2019)
print(index)
# check that reverse approach works
for line in reversed(data.splitlines()):
if "new stack" in line:
index = -(index - len + 1)
elif "cut" in line:
(cut,) = str_to_ints(line)
cut = (len + cut) % len
index = (index + cut) % len
elif "increment" in line:
(n,) = str_to_ints(line)
assert gcd(n, len) == 1
m = mod_inverse(n, len)
index = (index * m) % len
assert index == orig_index
lines = list(reversed(data.splitlines()))
# new length of deck
len = 119315717514047
# get expression for one loop using sympy
from sympy import symbols, simplify
index = symbols("index")
expr = index
for line in lines:
if "new stack" in line:
# index = -(index - len + 1)
expr = -(expr - len + 1)
elif "cut" in line:
(cut,) = str_to_ints(line)
cut = (len + cut) % len
# index = (index + cut) % len
expr = expr + cut
elif "increment" in line:
(n,) = str_to_ints(line)
assert gcd(n, len) == 1
m = mod_inverse(n, len)
# index = (index * m) % len
expr = expr * m
# we can see that expression is in the form (a - b * i) % m
expr = simplify(expr % len)
coeff_dict = expr.args[0].as_coefficients_dict()
a = coeff_dict[1]
b = -coeff_dict[index]
# math
n_shuffles = 101741582076661
r0 = 2020
m = len
p = (-b) % m
p_t = pow(p, n_shuffles, m)
inv_b1 = mod_inverse(b + 1, m)
term1 = (p_t * r0) % m
term2 = (a * (1 - p_t) * inv_b1) % m
r_t = (term1 + term2) % m
print(r_t)

47
2019/d23.py Normal file
View File

@@ -0,0 +1,47 @@
from lib import get_data, str_to_ints
from d9 import Amp
xs = str_to_ints(get_data(__file__))
first_255 = True
nat = None
y_prev = None
amps = []
for i in range(50):
a = Amp(xs)
a.feed(i)
a.go()
amps.append(a)
for j in range(1_000_000):
was_active = False
for i, a in enumerate(amps):
a.go()
if a.input_required:
a.feed(-1)
if len(a.outputs) > 0:
was_active = True
while len(a.outputs) < 3:
a.go()
addr = a.pop()
x = a.pop()
y = a.pop()
if addr == 255:
nat = (x, y)
if first_255:
print(y)
first_255 = False
else:
amps[addr].feed(x)
amps[addr].feed(y)
if not was_active and nat is not None:
x, y = nat
if y == y_prev:
print(y)
exit()
amps[0].feed(x)
amps[0].feed(y)
nat = None
y_prev = y

107
2019/d24.py Normal file
View File

@@ -0,0 +1,107 @@
from lib import get_data, Grid2D, add2
from collections import defaultdict
data = get_data(__file__)
g = Grid2D(data)
seen = set()
while True:
h = g.hash()
if h in seen:
break
else:
seen.add(h)
gn = g.clone_with_val(".")
for r in range(g.n_rows):
for c in range(g.n_cols):
p = (r, c)
nb_bugs = sum(1 for nb in g.neighbors_ort(p) if g[nb] == "#")
if g[p] == "#" and nb_bugs == 1:
gn[p] = "#"
if g[p] == "." and nb_bugs in [1, 2]:
gn[p] = "#"
g = gn
t = 0
p = 1
for r in range(g.n_rows):
for c in range(g.n_cols):
if g[(r, c)] == "#":
t += p
p *= 2
print(t)
N = (-1, 0)
E = (0, 1)
S = (1, 0)
W = (0, -1)
DIRS = [N, E, S, W]
ROW, COL = 0, 1
# data = get_data(__file__)
g = Grid2D(data)
bugs = [(0, r, c) for (r, c) in g.find("#")]
middle = (g.n_rows // 2, g.n_cols // 2)
rows, cols = g.n_rows, g.n_cols
def get_edge(side):
edges = {
S: [(0, c) for c in range(cols)],
N: [(rows - 1, c) for c in range(cols)],
W: [(r, cols - 1) for r in range(rows)],
E: [(r, 0) for r in range(rows)],
}
return edges[side]
def get_neighbors(pos):
level, row, col = pos
neighbors = []
for d in DIRS:
nb = add2((row, col), d)
if nb == middle:
for ir, ic in get_edge(d):
neighbors.append((level - 1, ir, ic))
elif nb[ROW] in {-1, rows} or nb[COL] in {-1, cols}:
nr, nc = nb
if nr == -1:
neighbors.append((level + 1, *add2(middle, N)))
if nr == rows:
neighbors.append((level + 1, *add2(middle, S)))
if nc == -1:
neighbors.append((level + 1, *add2(middle, W)))
if nc == cols:
neighbors.append((level + 1, *add2(middle, E)))
else:
nr, nc = nb
neighbors.append((level, nr, nc))
return neighbors
assert len(get_neighbors((0, 1, 1))) == 4
assert len(get_neighbors((0, 3, 3))) == 4
assert len(get_neighbors((0, 0, 3))) == 4
assert len(get_neighbors((0, 0, 4))) == 4
assert len(get_neighbors((0, 2, 3))) == 8
for _ in range(200):
neighbors = defaultdict(int)
current_bugs = set(bugs)
for pos in bugs:
for nb in get_neighbors(pos):
neighbors[nb] += 1
new_bugs = []
for pos, count in neighbors.items():
if pos in current_bugs and count == 1:
new_bugs.append(pos)
if pos not in current_bugs and count in [1, 2]:
new_bugs.append(pos)
bugs = new_bugs
print(len(bugs))

160
2019/d25.py Normal file
View File

@@ -0,0 +1,160 @@
from lib import get_data, ints
from d9 import Amp
from itertools import combinations
data = get_data(__file__)
xs = ints(data)
commands = [
"north",
"take candy cane",
"west",
"south",
"south",
"take fuel cell",
"south",
"take manifold",
"north",
"north",
"west",
"take mutex",
"north",
"south",
"south",
"south",
"take coin",
"south",
"north",
"east",
"take cake",
"west",
"east",
"north",
"south",
"west",
"east",
"east",
"north",
"south",
"west",
"north",
"west",
"east",
"west",
"south",
"west",
"north",
"north",
"south",
"west",
"north",
"east",
"south",
"west",
"east",
"west",
"south",
"west",
"take dehydrated water",
"west",
"east",
"west",
"south",
"take prime number",
"south",
"north",
"east",
"east",
"west",
"north",
"east",
"east",
"north",
"south",
"west",
"north",
"west",
"south",
"inv",
"west",
]
items = [
"cake",
"prime number",
"mutex",
"dehydrated water",
"coin",
"manifold",
"candy cane",
"fuel cell",
]
def run_command(a, s):
for c in s:
a.feed(ord(c))
a.feed(10)
a.go()
a = Amp(xs)
for c in commands:
a.go()
while a.outputs:
a.go()
a.pop()
# print(chr(a.pop()), end="")
run_command(a, c)
a.go()
def try_all(a):
subsets = [list(combinations(items, r)) for r in range(1, len(items) + 1)]
subsets = [item for sublist in subsets for item in sublist]
for subset in subsets:
for item in items:
c = f"drop {item}"
run_command(a, c)
a.go()
while a.outputs:
a.pop()
a.go()
for item in subset:
c = f"take {item}"
run_command(a, c)
a.go()
while a.outputs:
a.pop()
a.go()
run_command(a, "west")
output_str = ""
while a.outputs:
a.go()
output_str += chr(a.pop())
if "lighter" in output_str:
pass
elif "heavier" in output_str:
pass
else:
(password,) = ints(output_str)
print(password)
exit()
while True:
a.go()
output_str = ""
while a.outputs:
a.go()
output_str += chr(a.pop())
try_all(a)
if a.input_required:
c = input(">")
run_command(a, c)

20
2021/d1.py Normal file
View File

@@ -0,0 +1,20 @@
from lib import get_data
from lib import ints
data = get_data(__file__)
xs = ints(data)
t = 0
for a, b in zip(xs, xs[1:]):
if b > a:
t += 1
print(t)
t = 0
prev_sum = 10**12
for i in range(len(xs) - 2):
s = sum(xs[i : i + 3])
if s > prev_sum:
t += 1
prev_sum = s
print(t)

63
2021/d10.py Normal file
View File

@@ -0,0 +1,63 @@
from lib import get_data
data = get_data(__file__).strip()
OPEN = "([{<"
CLOSE = ")]}>"
def part1(line):
score = {
")": 3,
"]": 57,
"}": 1197,
">": 25137,
}
stack = []
for c in line:
if c in OPEN:
stack.append(OPEN.index(c))
elif c in CLOSE:
ci = CLOSE.index(c)
if stack and stack[-1] == ci:
stack.pop()
continue
else:
return score[c]
else:
assert False
return 0
def part2(line):
stack = []
for c in line:
if c in OPEN:
stack.append(OPEN.index(c))
elif c in CLOSE:
ci = CLOSE.index(c)
if stack and stack[-1] == ci:
stack.pop()
continue
else:
assert False
else:
assert False
score = 0
for v in reversed(stack):
score *= 5
score += v + 1
return score
s1 = 0
s2s = []
for line in data.splitlines():
s = part1(line)
if s == 0:
s2s.append(part2(line))
s1 += s
print(s1)
s2 = sorted(s2s)[len(s2s) // 2]
print(s2)

37
2021/d11.py Normal file
View File

@@ -0,0 +1,37 @@
from lib import get_data
from lib import Grid2D
data = get_data(__file__).strip()
g = Grid2D(data)
flashes_100 = 0
all_flash_round = None
for i in range(10**9):
for row in range(g.n_rows):
for col in range(g.n_cols):
g[(row, col)] = str(int(g[(row, col)]) + 1)
has_flashed = set()
more_flashes = True
while more_flashes:
more_flashes = False
for row in range(g.n_rows):
for col in range(g.n_cols):
level = int(g[(row, col)])
if level > 9 and (row, col) not in has_flashed:
more_flashes = True
for nb in g.neighbors_adj((row, col)):
g[nb] = str(int(g[nb]) + 1)
has_flashed.add((row, col))
for oct in has_flashed:
g[oct] = "0"
if i < 100:
flashes_100 += len(has_flashed)
if len(has_flashed) == g.n_rows * g.n_cols:
all_flash_round = i + 1
break
print(flashes_100)
print(all_flash_round)

56
2021/d12.py Normal file
View File

@@ -0,0 +1,56 @@
from lib import get_data
from lib import Grid2D
from lib import ints
from collections import deque
from collections import defaultdict
data = get_data(__file__).strip()
g = defaultdict(list)
for line in data.splitlines():
a, b = line.strip().split("-")
g[a].append(b)
g[b].append(a)
def no_doubles(path):
path = [p for p in path if p.islower()]
return len(path) == len(set(path))
for part in [1, 2]:
start = ("start", ())
visited = set()
queue = deque([start])
total = 0
while queue:
vertex = queue.popleft()
if vertex in visited:
continue
visited.add(vertex)
current, path = vertex
neighbors = []
for neighbor in g[current]:
if neighbor == "end":
total += 1
elif neighbor == "start":
continue
elif neighbor.islower():
if neighbor not in path or (part == 2 and no_doubles(path)):
new_path = tuple(list(path) + [neighbor])
nb = (neighbor, new_path)
if nb not in visited:
queue.append(nb)
elif neighbor.isupper():
new_path = tuple(list(path) + [neighbor])
nb = (neighbor, new_path)
if nb not in visited:
queue.append(nb)
else:
assert False
print(total)

51
2021/d13.py Normal file
View File

@@ -0,0 +1,51 @@
from lib import get_data
from lib import ints
data = get_data(__file__).strip()
def print_from_xy(xs):
x_min = min(v[0] for v in xs)
x_max = max(v[0] for v in xs)
y_min = min(v[1] for v in xs)
y_max = max(v[1] for v in xs)
for y in range(y_min, y_max + 1):
row = ""
for x in range(x_min, x_max + 1):
if (x, y) in xs:
row += "#"
else:
row += " "
print(row)
p1, p2 = data.split("\n\n")
xs = set([tuple(ints(line)) for line in p1.splitlines()])
folds = []
for line in p2.splitlines():
d = "x" if "x=" in line else "y"
(n,) = ints(line)
folds.append((d, n))
first = None
for d, n in folds:
if d == "x":
to_move = [(x, y) for (x, y) in xs if x > n]
for x, y in to_move:
xs.remove((x, y))
new_x = n - (x - n)
xs.add((new_x, y))
elif d == "y":
to_move = [(x, y) for (x, y) in xs if y > n]
for x, y in to_move:
xs.remove((x, y))
new_y = n - (y - n)
xs.add((x, new_y))
else:
assert False
first = first or len(xs)
print(first)
print_from_xy(xs)

43
2021/d14.py Normal file
View File

@@ -0,0 +1,43 @@
from lib import get_data
from itertools import pairwise
from collections import defaultdict
data = get_data(__file__)
orig_t, p2 = data.split("\n\n")
pairs = {}
for line in p2.splitlines():
ls, rs = line.split(" -> ")
assert ls not in pairs
pairs[ls] = rs
for steps in [10, 40]:
t = str(orig_t)
start_letter = t[0]
end_letter = t[-1]
td = defaultdict(int)
for a, b in pairwise(t):
td[a + b] += 1
for _ in range(steps):
ntd = defaultdict(int)
for pair, count in td.items():
if pair in pairs:
a, b = pair
c = pairs[pair]
ntd[a + c] += count
ntd[c + b] += count
else:
ntd[pair] += count
td = ntd
counts = defaultdict(int)
for (a, b), count in td.items():
counts[a] += count
counts[b] += count
counts = {k: v // 2 for k, v in counts.items()}
counts[start_letter] += 1
counts[end_letter] += 1
r = max(counts.values()) - min(counts.values())
print(r)

83
2021/d15.py Normal file
View File

@@ -0,0 +1,83 @@
from lib import get_data
from lib import Grid2D
import heapq
data = get_data(__file__)
def new_data(data, repeat=5):
new_lines = []
for line in data.splitlines():
new_line = str(line)
for i in range(len(line) * repeat):
ni = i - len(line)
if ni < 0:
continue
nc = int(new_line[ni]) + 1
if nc == 10:
nc = 1
new_line += str(nc)
new_lines.append(new_line)
for i in range(len(data.splitlines()) * repeat):
ni = i - len(data.splitlines())
if ni < 0:
continue
line = list(new_lines[ni])
for i in range(len(line)):
nc = int(line[i]) + 1
if nc == 10:
nc = 1
line[i] = str(nc)
line = "".join(line)
new_lines.append(line)
data = "\n".join(new_lines)
return data
big_data = new_data(data)
for data in [data, big_data]:
g = Grid2D(data)
def dist(n1, n2):
"""cost from node to node"""
if n1 == 0:
return 0
return int(g[n2])
def h(node):
"""heuristic function (never overestimate)"""
return abs(g.n_rows - 1 - node[0]) + abs(g.n_cols - 1 - node[1])
def is_goal(node):
return node == (g.n_rows - 1, g.n_cols - 1)
def neighbors(node):
return list(g.neighbors_ort(node))
starts = [(0, 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)

85
2021/d16.py Normal file
View File

@@ -0,0 +1,85 @@
from lib import get_data
data = get_data(__file__)
bs = ""
for c in data.strip():
b = bin(int(c, 16))[2:]
b = "0" * (4 - len(b)) + b
bs += b
total_v = 0
def parse(bs: str, i: int):
v = int(bs[i : i + 3], 2)
global total_v
total_v += v
i += 3
t = int(bs[i : i + 3], 2)
i += 3
# print(v, t)
if t == 4:
lv = ""
while True:
seg = bs[i : i + 5]
lv += seg[1:]
i += 5
if seg[0] == "0":
break
lv = int(lv, 2)
return i, lv
vs = []
if bs[i] == "0":
# total length of bits
i += 1
bits = int(bs[i : i + 15], 2)
i += 15
i_max = i + bits
while True:
i, v = parse(bs, i)
vs.append(v)
if i >= i_max:
break
elif bs[i] == "1":
# number of sub-packets
i += 1
num_sub = int(bs[i : i + 11], 2)
i += 11
vs = []
for _ in range(num_sub):
i, v = parse(bs, i)
vs.append(v)
else:
assert False
if t == 0:
return i, sum(vs)
elif t == 1:
x = 1
for v in vs:
x *= v
return i, x
elif t == 2:
return i, min(vs)
elif t == 3:
return i, max(vs)
elif t == 5:
assert len(vs) == 2
return i, 1 if vs[0] > vs[1] else 0
elif t == 6:
assert len(vs) == 2
return i, 1 if vs[0] < vs[1] else 0
elif t == 7:
assert len(vs) == 2
return i, 1 if vs[0] == vs[1] else 0
else:
assert False
p2 = parse(bs, 0)[1]
print(total_v)
print(p2)

35
2021/d17.py Normal file
View File

@@ -0,0 +1,35 @@
from lib import get_data
from lib import ints
data = get_data(__file__)
x_min, x_max, y_min, y_max = ints(data)
def simulate(x, y, vx, vy):
height_max = 0
while x <= x_max and y >= y_min:
height_max = max(y, height_max)
if x_min <= x <= x_max and y_min <= y <= y_max:
return True, height_max
x += vx
y += vy
if vx != 0:
vx = vx - 1 if vx > 0 else vx + 1
vy -= 1
return False, 0
height_max = 0
total = set()
for vx in range(1, 2000):
for vy in range(-2000, 2000):
in_target, height = simulate(0, 0, vx, vy)
if in_target:
total.add((vx, vy))
if in_target and height > height_max:
height_max = height
print(height_max)
print(len(total))

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)

121
2021/d19.py Normal file
View File

@@ -0,0 +1,121 @@
from lib import get_data
from lib import ints
from collections import defaultdict
from itertools import permutations, product
V3 = tuple[int, int, int]
data = get_data(__file__)
scanners = []
for block in data.split("\n\n"):
scanner = []
for line in block.splitlines()[1:]:
a, b, c = ints(line)
scanner.append((a, b, c))
scanners.append(scanner)
def rotate(vi: V3) -> list[V3]:
r = []
for p in permutations(vi):
for f in product([1, -1], repeat=3):
v = tuple([a * b for a, b in zip(p, f)])
r.append(v)
return r
assert len(rotate((8, 0, 7))) == 48
def sub(a: V3, b: V3) -> V3:
return a[0] - b[0], a[1] - b[1], a[2] - b[2]
def add(a: V3, b: V3) -> V3:
return a[0] + b[0], a[1] + b[1], a[2] + b[2]
def relative(scanner: list[V3]):
d = defaultdict(list)
for i in range(len(scanner)):
for j in range(i + 1, len(scanner)):
for ii, jj in [(i, j), (j, i)]:
a, b = scanner[ii], scanner[jj]
delta = sub(a, b)
d[delta].append((a, b))
return d
def overlap(scanner_1, scanner_2):
expected_overlaps = 15
r1 = relative(scanner_1)
scanners_2 = []
for scanner in list(zip(*list(map(rotate, scanner_2)))):
os = set()
r2 = relative(scanner)
# number of bacon pairs that have the same offset
t = sum(1 for k1 in r1.keys() if k1 in r2)
if t >= expected_overlaps:
for k1, v1 in r1.items():
if k1 in r2:
((abs1, abs2),) = v1
((rel1, rel2),) = r2[k1]
os.add(sub(abs1, rel1))
os.add(sub(abs2, rel2))
if len(os) == 1:
# found the right orientation for scanner_2
scanners_2.append((os.pop(), scanner))
else:
r2 = None
else:
r2 = None
if len(scanners_2) == 0:
return None
((orig_2, scanner_2_rel),) = scanners_2
scanner_2_abs = [add(orig_2, b) for b in scanner_2_rel]
return orig_2, scanner_2_abs
origs = [(0, 0, 0)]
todo = set(range(len(scanners)))
done = set([0]) if 0 in todo else set([todo.pop()])
todo.discard(0)
while todo:
for i in range(len(scanners)):
for j in range(len(scanners)):
if i == j:
continue
if i not in done:
continue
if j in done:
continue
r = overlap(scanners[i], scanners[j])
if r is None:
continue
o, s2 = r
origs.append(o)
no = len(set(scanners[i]).intersection(s2))
if no >= 12:
scanners[j] = s2
done.add(j)
todo.discard(j)
# print(f"{i} < {no} > {j} at {o}")
all = []
for s in scanners:
all += s
print(len(set(all)))
def mdist(a, b):
return sum(abs(aa - bb) for aa, bb in zip(a, b))
m = 0
for i in range(len(origs)):
for j in range(i + 1, len(origs)):
m = max(m, mdist(origs[i], origs[j]))
print(m)

24
2021/d2.py Normal file
View File

@@ -0,0 +1,24 @@
from lib import get_data
from lib import ints
data = get_data(__file__)
aim, h2, d2 = 0, 0, 0
h, d = 0, 0
for line in data.splitlines():
(v,) = ints(line)
if "forward" in line:
h2 += v
d2 += aim * v
h += v
elif "down" in line:
aim += v
d += v
elif "up" in line:
aim -= v
d -= v
else:
assert False
print(h * d)
print(h2 * d2)

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))

92
2021/d22.py Normal file
View File

@@ -0,0 +1,92 @@
from lib import get_data
from lib import ints
from dataclasses import dataclass
from typing import Optional
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))
@dataclass
class Line:
a: int
b: int
def intersects(self, other: "Line") -> bool:
return self.b > other.a and self.a < other.b
def intersection(self, other: "Line") -> Optional["Line"]:
if not self.intersects(other):
return None
return Line(max(self.a, other.a), min(self.b, other.b))
def length(self) -> int:
assert self.b > self.a
return self.b - self.a + 1
@dataclass(frozen=True)
class Cube:
on: bool
x: Line
y: Line
z: Line
@classmethod
def from_line(cls, line: str) -> "Cube":
xs = ints(line)
assert len(xs) == 6
lines = [Line(*xs[i:i+2]) for i in range(0, len(xs), 2)]
on = True if line.startswith("on") else False
return Cube(on, *lines)
def volume(self) -> int:
return (self.x.length() * self.y.length() * self.z.length()) * (1 if self.on else -1)
def intersects(self, other: "Cube") -> bool:
return (
self.x.intersects(other.x)
and self.y.intersects(other.y)
and self.z.intersects(other.z)
)
def intersection(self, other: "Cube") -> Optional["Cube"]:
x = self.x.intersection(other.x)
y = self.y.intersection(other.y)
z = self.z.intersection(other.z)
if x is None or y is None or z is None:
return None
return Cube(not self.on, x, y, z)
# Inclusion/exclusion principle from set theory
cubes = []
for line in data.splitlines():
cc = Cube.from_line(line)
ncubes = []
for i in range(len(cubes)):
c = cubes[i]
ic = c.intersection(cc)
if ic is not None:
cubes.append(ic)
if cc.on:
cubes.append(cc)
print(sum(c.volume() for c in cubes))

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)

45
2021/d24.py Normal file
View File

@@ -0,0 +1,45 @@
from lib import get_data
data = get_data(__file__)
def run(number):
inputs = list(map(int, str(number)))
assert len(inputs) == 14
w, x, z = 0, 0, 0
i = 0
for i in range(len(inputs)):
w = inputs[i]
# print(f"{w=:3} {x=:3} {z=}")
to_add_x = [15, 15, 12, 13, -12, 10, -9, 14, 13, -14, -11, -2, -16, -14][i]
to_div_z = [1, 1, 1, 1, 26, 1, 26, 1, 1, 26, 26, 26, 26, 26][i]
to_add_z = [15, 10, 2, 16, 12, 11, 5, 16, 6, 15, 3, 12, 10, 13][i]
zz = z
x = (z % 26) + to_add_x
z = z // to_div_z
# print(f"{zz % 26=} + {to_add_x=} {x=}")
if x == w:
# print(f"added = 0")
pass
else:
z *= 26
z += w + to_add_z
# print(f"added {w=} + {to_add_z=} = {w + to_add_z}")
# print()
# print(f"\nEND:\n{w=} {x=} {z=}", "\n" * 15)
return z
largest_model_nr = 89959794919939
assert run(largest_model_nr) == 0
print(largest_model_nr)
lowest_model_nr = 17115131916112
assert run(lowest_model_nr) == 0
print(lowest_model_nr)

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"

56
2021/d3.py Normal file
View File

@@ -0,0 +1,56 @@
from lib import get_data
from lib import ints
from collections import defaultdict
data = get_data(__file__)
def count_one_zero(xs, index):
ones, zeros = 0, 0
for x in xs:
if x[index] == "1":
ones += 1
elif x[index] == "0":
zeros += 1
else:
assert False
return (ones, zeros)
a = ""
b = ""
lines = list(data.strip().splitlines())
for i in range(len(lines[0])):
ones, zeros = count_one_zero(lines, i)
if ones > zeros:
a += "1"
b += "0"
elif zeros > ones:
a += "0"
b += "1"
else:
assert False
a, b = int(a, 2), int(b, 2)
print(a * b)
lines = list(data.strip().splitlines())
for i in range(len(lines[0])):
ones, zeros = count_one_zero(lines, i)
c = "1" if ones >= zeros else "0"
lines = [l for l in lines if l[i] == c]
if len(lines) == 1:
break
(line,) = lines
a = int(line, 2)
lines = list(data.strip().splitlines())
for i in range(len(lines[0])):
ones, zeros = count_one_zero(lines, i)
c = "0" if zeros <= ones else "1"
lines = [l for l in lines if l[i] == c]
if len(lines) == 1:
break
(line,) = lines
b = int(line, 2)
print(a * b)

52
2021/d4.py Normal file
View File

@@ -0,0 +1,52 @@
from lib import get_data
from lib import ints
data = get_data(__file__).strip()
boards = []
numbers = None
for i, p in enumerate(data.split("\n\n")):
if i == 0:
numbers = ints(p)
else:
board = []
for line in p.splitlines():
board.append(ints(line))
boards.append(board)
assert numbers is not None
first_won = None
last_won = None
boards_done = list()
for n in numbers:
for i in range(len(boards)):
if i in boards_done:
continue
board = boards[i]
board = [[f if n != f else None for f in row] for row in board]
boards[i] = board
for row in board:
if all(cell is None for cell in row):
if not i in boards_done:
boards_done.append(i)
for col in zip(*board):
if all(cell is None for cell in col):
if not i in boards_done:
boards_done.append(i)
if len(boards_done) > 0 and first_won is None:
first_won = (i, n)
if len(boards_done) == len(boards):
last_won = (i, n)
break
if len(boards_done) == len(boards):
break
assert first_won is not None
assert last_won is not None
for i, n in [first_won, last_won]:
board = boards[i]
a = sum([sum([n if n is not None else 0 for n in row]) for row in board])
print(a * n)

39
2021/d5.py Normal file
View File

@@ -0,0 +1,39 @@
from lib import get_data
from lib import ints
from collections import defaultdict
data = get_data(__file__).strip()
counts = defaultdict(int)
for line in data.splitlines():
x1, y1, x2, y2 = ints(line)
if x1 == x2 or y1 == y2:
if x1 == x2:
for y in range(min(y1, y2), max(y1, y2) + 1):
counts[(x1, y)] += 1
elif y1 == y2:
for x in range(min(x1, x2), max(x1, x2) + 1):
counts[(x, y1)] += 1
else:
assert False
r = sum(1 for v in counts.values() if v > 1)
print(r)
for line in data.splitlines():
x1, y1, x2, y2 = ints(line)
if x1 == x2 or y1 == y2:
pass
else:
dx = 1 if x2 > x1 else -1
dy = 1 if y2 > y1 else -1
while (x1, y1) != (x2, y2):
counts[(x1, y1)] += 1
x1 += dx
y1 += dy
counts[(x2, y2)] += 1
r = sum(1 for v in counts.values() if v > 1)
print(r)

32
2021/d6.py Normal file
View File

@@ -0,0 +1,32 @@
from lib import get_data
from lib import ints
from collections import defaultdict
data = get_data(__file__).strip()
xs = ints(data)
for _ in range(80):
for i in range(len(xs)):
if xs[i] == 0:
xs[i] = 6
xs.append(8)
else:
xs[i] -= 1
print(len(xs))
xs = ints(data)
fish = defaultdict(int)
for x in xs:
fish[x] += 1
for _ in range(256):
new_fish = defaultdict(int)
new_fish[8] = fish[0]
new_fish[6] = fish[0]
for i in range(1, 9):
new_fish[i - 1] += fish[i]
fish = new_fish
r = sum(fish.values())
print(r)

20
2021/d7.py Normal file
View File

@@ -0,0 +1,20 @@
from lib import get_data
from lib import ints
data = get_data(__file__).strip()
xs = ints(data)
def d(t, x):
n = abs(t - x)
return n * (n + 1) // 2
s_min_1 = 10**16
s_min_2 = 10**16
for t in range(min(xs), max(xs) + 1):
s_min_1 = min(s_min_1, sum(abs(t - x) for x in xs))
s_min_2 = min(s_min_2, sum(d(t, x) for x in xs))
print(s_min_1)
print(s_min_2)

65
2021/d8.py Normal file
View File

@@ -0,0 +1,65 @@
from lib import get_data
from itertools import permutations
data = get_data(__file__).strip()
code_to_num = {
"cf": 1,
"acf": 7,
"bcdf": 4,
"acdeg": 2,
"acdfg": 3,
"abdfg": 5,
"abcefg": 0,
"abdefg": 6,
"abcdfg": 9,
"abcdefg": 8,
}
def translate(inn, mapping):
out = ""
for c in inn:
out += mapping[c]
return "".join(sorted(out))
def decode(inn, out):
s = "abcdefg"
m = None
for p in permutations(s):
m = {k: v for k, v in zip(p, s)}
all = set(code_to_num.keys())
for in_code in inn:
out_code = translate(in_code, m)
if out_code in all:
all.remove(out_code)
if len(all) == 0:
break
else:
m = None
assert m is not None
o = 0
d = 1000
for out_code in out:
out_code = translate(out_code, m)
v = code_to_num[out_code]
o += d * v
d //= 10
return o
s1 = 0
s2 = 0
for line in data.splitlines():
inn, out = line.strip().split(" | ")
inn = inn.split()
out = out.split()
s1 += sum(1 for v in out if len(v) in [2, 3, 4, 7])
s2 += decode(inn, out)
print(s1)
print(s2)

47
2021/d9.py Normal file
View File

@@ -0,0 +1,47 @@
from lib import get_data, Grid2D
from collections import deque
data = get_data(__file__).strip()
g = Grid2D(data)
s = 0
low_points = []
for row in range(g.n_rows):
for col in range(g.n_cols):
h = int(g[(row, col)])
for nb in g.neighbors_ort((row, col)):
if int(g[nb]) <= h:
break
else:
low_points.append((row, col))
s += 1 + h
print(s)
basins = []
for row, col in low_points:
start = None
visited = set()
queue = deque([(row, col)])
while queue:
vertex = queue.popleft()
if vertex in visited:
continue
visited.add(vertex)
neighbors = []
for neighbor in g.neighbors_ort(vertex):
if neighbor in visited:
continue
neighbor_height = int(g[neighbor])
if neighbor_height < 9:
queue.append(neighbor)
basins.append(len(visited))
s = 1
for x in sorted(basins)[-3:]:
s *= x
print(s)

1
2021/lib.py Symbolic link
View File

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

21
2024/d01.py Normal file
View File

@@ -0,0 +1,21 @@
from lib import get_data
from lib import ints
data = get_data(__file__)
s = 0
ass, bss = [], []
s2 = 0
# I had an intuition for this and then went for loops...
xs, ys = list(zip(*[ints(line) for line in data.splitlines()]))
xs = sorted(xs)
ys = sorted(ys)
for a, b in zip(xs, ys):
s += max(a, b) - min(a, b)
s2 += ys.count(a) * a
print(s)
print(s2)

42
2024/d02.py Normal file
View File

@@ -0,0 +1,42 @@
from lib import get_data
from lib import ints
data = get_data(__file__)
s1 = 0
s2 = 0
for line in data.splitlines():
xs = ints(line)
deltas = []
for i in range(len(xs) - 1):
deltas.append(xs[i + 1] - xs[i])
if abs(xs[i + 1] - xs[i]) not in [1, 2, 3]:
break
else:
if deltas[0] < 0 and all(deltas[i] < 0 for i in range(len(deltas))):
s1 += 1
elif deltas[0] > 0 and all(deltas[i] > 0 for i in range(len(deltas))):
s1 += 1
sublists = [xs[:i] + xs[i + 1 :] for i in range(len(xs))]
found = False
for xs in sublists:
deltas = []
for i in range(len(xs) - 1):
deltas.append(xs[i + 1] - xs[i])
if abs(xs[i + 1] - xs[i]) not in [1, 2, 3]:
break
else:
if deltas[0] < 0 and all(deltas[i] < 0 for i in range(len(deltas))):
found = True
if deltas[0] > 0 and all(deltas[i] > 0 for i in range(len(deltas))):
found = True
if found:
s2 += 1
print(s1)
print(s2)

24
2024/d03.py Normal file
View File

@@ -0,0 +1,24 @@
import re
from lib import get_data
from lib import ints
data = get_data(__file__)
t1 = 0
t2 = 0
enabled = True
r = re.compile(r"mul\(\d+,\d+\)|do\(\)|don't\(\)")
for m in r.findall(data):
if m == "do()":
enabled = True
elif m == "don't()":
enabled = False
else:
a, b = ints(m)
t1 += a * b
if enabled:
t2 += a * b
print(t1)
print(t2)

44
2024/d04.py Normal file
View File

@@ -0,0 +1,44 @@
from lib import get_data
from lib import Grid2D
data = get_data(__file__)
g = Grid2D(data)
DIRS = {
"N": (-1, 0),
"E": (0, 1),
"S": (1, 0),
"W": (0, -1),
"NE": (-1, 1),
"SE": (1, 1),
"SW": (1, -1),
"NW": (-1, -1),
}
t1, t2 = 0, 0
for row in range(g.n_rows):
for col in range(g.n_cols):
for d in DIRS.values():
coords = [(row + d[0] * i, col + d[1] * i) for i in range(len("XMAS"))]
for (r, c), z in zip(coords, "XMAS"):
if not (0 <= r < g.n_rows and 0 <= c < g.n_cols and g[(r, c)] == z):
break
else:
t1 += 1
coords = [
(row - 1, col - 1),
(row - 1, col + 1),
(row, col),
(row + 1, col - 1),
(row + 1, col + 1),
]
for s in ["MMASS", "MSAMS", "SSAMM", "SMASM"]:
for (r, c), z in zip(coords, s):
if not (0 <= r < g.n_rows and 0 <= c < g.n_cols and g[(r, c)] == z):
break
else:
t2 += 1
print(t1)
print(t2)

37
2024/d05.py Normal file
View File

@@ -0,0 +1,37 @@
from lib import get_data
from lib import ints
def fix(xs):
change = True
while change:
change = False
for rule in rules.splitlines():
x, y = ints(rule)
if not (x in xs and y in xs):
continue
i, j = xs.index(x), xs.index(y)
if not i < j:
xs[i], xs[j] = xs[j], xs[i]
change = True
return xs
rules, pages = get_data(__file__).split("\n\n")
t1, t2 = 0, 0
for page in pages.splitlines():
xs = ints(page)
for rule in rules.splitlines():
x, y = ints(rule)
if not (x in xs and y in xs):
continue
if not (xs.index(x) < xs.index(y)):
xs = fix(ints(page))
t2 += xs[len(xs) // 2]
break
else:
t1 += xs[len(xs) // 2]
print(t1)
print(t2)

51
2024/d06.py Normal file
View File

@@ -0,0 +1,51 @@
from lib import get_data
from lib import Grid2D
data = get_data(__file__)
DIRS = [
(-1, 0),
(0, 1),
(1, 0),
(0, -1),
]
g = Grid2D(data)
t = 0
for rr in range(g.n_rows):
for cc in range(g.n_cols):
(p,), dir = g.find("^"), 0
seen = set()
loop = False
if g[(rr, cc)] == "#":
continue
if g[(rr, cc)] != "^":
g[(rr, cc)] = "#"
while True:
if (p, dir) in seen:
loop = True
break
seen.add((p, dir))
r, c = p[0] + DIRS[dir][0], p[1] + DIRS[dir][1]
if not (0 <= r < g.n_rows and 0 <= c < g.n_cols):
break
if g[(r, c)] == "#":
dir = (dir + 1) % 4
else:
p = (r, c)
if g[(rr, cc)] == "^":
print(len(set(pos for pos, _ in seen)))
else:
g[(rr, cc)] = "."
if loop:
t += 1
print(t)

30
2024/d07.py Normal file
View File

@@ -0,0 +1,30 @@
from lib import get_data
from lib import ints
data = get_data(__file__)
def combs(acc, rest, part_2=False):
if rest == []:
return acc
nacc = []
for a in acc:
nacc.append(a + rest[0])
nacc.append(a * rest[0])
if part_2:
nacc.append(int(str(a) + str(rest[0])))
return combs(nacc, rest[1:], part_2)
t1, t2 = 0, 0
for line in data.splitlines():
xs = ints(line)
expected = xs[0]
if xs[0] in combs([xs[1]], xs[2:]):
t1 += xs[0]
if xs[0] in combs([xs[1]], xs[2:], True):
t2 += xs[0]
print(t1)
print(t2)

34
2024/d08.py Normal file
View File

@@ -0,0 +1,34 @@
from lib import get_data
from lib import Grid2D
from itertools import combinations
from collections import defaultdict
data = get_data(__file__)
g = Grid2D(data)
d = defaultdict(list)
for row in range(g.n_rows):
for col in range(g.n_cols):
p = (row, col)
if g[p] != ".":
d[g[p]].append((row, col))
aa = set()
bb = set()
for coords in d.values():
for c, d in combinations(coords, 2):
bb.add(c)
bb.add(d)
for a, b in [(c, d), (d, c)]:
d = b[0] - a[0], b[1] - a[1]
a = b[0] + d[0], b[1] + d[1]
if g.contains(a):
aa.add(a)
a = b[0] + d[0], b[1] + d[1]
while g.contains(a):
bb.add(a)
a = a[0] + d[0], a[1] + d[1]
print(len(aa))
print(len(bb))

102
2024/d09.py Normal file
View File

@@ -0,0 +1,102 @@
from lib import get_data
from copy import deepcopy
data = get_data(__file__)
i = 0
f = True
d = []
spaces = 0
for c in data.strip():
if f:
d.append([int(c), i, f])
f = False
i += 1
else:
spaces += int(c)
d.append([int(c), -1, f])
f = True
d2 = deepcopy(d)
BLOCKS, ID, FULL = 0, 1, 2
empty_i = 1
full_i = len(d) - 1
assert d[full_i][FULL]
while empty_i < full_i:
empty = d[empty_i]
full = d[full_i]
if empty[BLOCKS] == full[BLOCKS]:
empty[ID] = full[ID]
empty[FULL] = True
full[BLOCKS] = 0
full[FULL] = False
elif empty[BLOCKS] < full[BLOCKS]:
empty[ID] = full[ID]
empty[FULL] = True
full[BLOCKS] -= empty[BLOCKS]
elif empty[BLOCKS] > full[BLOCKS]:
new = [empty[BLOCKS] - full[BLOCKS], empty[ID], False]
empty[ID] = full[ID]
empty[BLOCKS] = full[BLOCKS]
empty[FULL] = True
d.insert(empty_i + 1, new)
full_i += 1
full[BLOCKS] = 0
full[FULL] = False
else:
assert False
while d[empty_i][FULL] == True:
empty_i += 1
while d[full_i][FULL] == False:
full_i -= 1
t = 0
i = 0
for b in d:
if b[FULL] == False:
break
for _ in range(b[BLOCKS]):
t += i * b[ID]
i += 1
print(t)
d = d2
full_i = len(d) - 1
while full_i > 0:
full = d[full_i]
prev_i = full_i
for empty_i in range(0, full_i):
empty = d[empty_i]
if full[FULL] == False:
continue
if empty[FULL]:
continue
if empty[BLOCKS] < full[BLOCKS]:
continue
if empty[BLOCKS] == full[BLOCKS]:
empty[FULL] = True
empty[ID] = full[ID]
full[FULL] = False
elif empty[BLOCKS] > full[BLOCKS]:
full[FULL] = False
new = [full[BLOCKS], full[ID], True]
d[empty_i] = new
d.insert(empty_i + 1, [empty[BLOCKS] - full[BLOCKS], -1, False])
full_i += 1
break
full_i -= 1
t = 0
i = 0
for b in d:
for _ in range(b[BLOCKS]):
if b[FULL] == True:
t += i * b[ID]
i += 1
print(t)

81
2024/d09_2.py Normal file
View File

@@ -0,0 +1,81 @@
from lib import get_data
data = get_data("d9.py")
def parse(data):
xs = []
f = True
i = 0
for c in data.strip():
if f:
for _ in range(int(c)):
xs.append(i)
f = False
i += 1
else:
for _ in range(int(c)):
xs.append(".")
f = True
return xs
def score(xs):
t = 0
for i, j in enumerate(xs):
if j == ".":
continue
t += i * j
return t
xs = parse(data)
i, j = 0, len(xs) - 1
while i <= j:
while xs[i] != ".":
i += 1
while xs[j] == ".":
j -= 1
if not i <= j:
break
xs[i], xs[j] = xs[j], xs[i]
print(score(xs))
xs = parse(data)
idx = {}
ids = []
for i, x in enumerate(xs):
if x == ".":
continue
if not x in idx:
idx[x] = i
ids.append(x)
while ids:
id = ids.pop()
i1, i2 = idx[id], idx[id]
while i2 < len(xs) and xs[i2] == id:
i2 += 1
j1 = 0
while j1 < i1:
while xs[j1] != ".":
j1 += 1
if not j1 < i1:
break
j2 = j1
while xs[j2] == ".":
j2 += 1
assert j2 <= i1
if i2 - i1 <= j2 - j1:
for j, i in zip(range(j1, j2), range(i1, i2)):
xs[j], xs[i] = xs[i], xs[j]
break
j1 = j2
print(score(xs))

26
2024/d10.py Normal file
View File

@@ -0,0 +1,26 @@
from collections import deque
from lib import Grid2D
from lib import get_data
data = get_data(__file__)
g = Grid2D(data)
p1 = 0
p2 = 0
for start in g.find("0"):
queue = deque([start])
all = set()
while queue:
node = queue.popleft()
if g[node] == "9":
all.add(node)
p2 += 1
for nb in g.neighbors_ort(node):
if int(g[node]) + 1 == int(g[nb]):
queue.append(nb)
p1 += len(all)
print(p1)
print(p2)

31
2024/d11.py Normal file
View File

@@ -0,0 +1,31 @@
from lib import get_data
from lib import ints
data = get_data(__file__)
xs = ints(data)
CACHE = {}
# I had maxsize too small at first :/
# @lru_cache(maxsize=10**12)
def apply(x, rest):
if rest == 0:
return 1
if (x, rest) in CACHE:
return CACHE[(x, rest)]
if x == 0:
r = apply(1, rest - 1)
elif len(str(x)) % 2 == 0:
sx = str(x)
a = int(sx[: len(sx) // 2])
b = int(sx[len(sx) // 2 :])
r = apply(a, rest - 1) + apply(b, rest - 1)
else:
r = apply(x * 2024, rest - 1)
CACHE[(x, rest)] = r
return r
print(sum([apply(x, 25) for x in xs]))
print(sum([apply(x, 75) for x in xs]))

61
2024/d12.py Normal file
View File

@@ -0,0 +1,61 @@
from lib import get_data
from lib import Grid2D
from collections import defaultdict
from collections import deque
data = get_data(__file__)
DIRS = {
"N": (-1, 0),
"E": (0, 1),
"S": (1, 0),
"W": (0, -1),
}.values()
g = Grid2D(data)
cs = defaultdict(set)
for row in range(g.n_rows):
for col in range(g.n_cols):
c = g[(row, col)]
cs[c].add((row, col))
t1, t2 = 0, 0
for c, cs in cs.items():
while cs:
peri = 0
start = cs.pop()
seen = set()
queue = deque([start])
fences = set()
while queue:
pos = queue.popleft()
if pos in seen:
continue
seen.add(pos)
for d in DIRS:
nb = d[0] + pos[0], d[1] + pos[1]
if g.contains(nb) and g[nb] == c:
queue.append(nb)
cs.discard(nb)
else:
peri += 1
nb = tuple(list(nb) + [d])
fences.add(nb)
nfences = set()
for row, col, dir in fences:
for dr, dc in [(1, 0), (0, 1)]:
nr, nc = row + dr, col + dc
if (nr, nc, dir) in fences:
break
else:
nfences.add((row, col, dir))
c_area = len(seen)
t1 += c_area * peri
t2 += c_area * len(nfences)
print(t1)
print(t2)

33
2024/d13.py Normal file
View File

@@ -0,0 +1,33 @@
from lib import get_data
from lib import ints
data = get_data(__file__)
ps = data.split("\n\n")
c1, c2 = 0, 0
for p in ps:
lines = p.splitlines()
ax, ay = ints(lines[0])
bx, by = ints(lines[1])
px, py = ints(lines[2])
px2 = px + 10000000000000
py2 = py + 10000000000000
for a in range(0, 101):
for b in range(0, 101):
if a * ax + b * bx == px and a * ay + b * by == py:
c1 += a * 3 + b
D = ax * by - bx * ay
if abs(D) > 1e-10:
a_ = px2 * by - bx * py2
b_ = ax * py2 - px2 * ay
if a_ % D == 0 and b_ % D == 0:
a = (px2 * by - bx * py2) // D
b = (ax * py2 - px2 * ay) // D
if a >= 0 and b >= 0:
c2 += a * 3 + b
print(c1)
print(c2)

64
2024/d14.py Normal file
View File

@@ -0,0 +1,64 @@
from lib import get_data
from lib import ints
from collections import defaultdict
rows = 103
cols = 101
data = get_data(__file__)
def score(robots):
quadrants = defaultdict(int)
for _, r, c in robots:
if r < rows // 2 and c < cols // 2:
quadrants[1] += 1
elif r < rows // 2 and c > cols // 2:
quadrants[2] += 1
elif r > rows // 2 and c < cols // 2:
quadrants[3] += 1
elif r > rows // 2 and c > cols // 2:
quadrants[4] += 1
t = 1
for q in quadrants.values():
t *= q
return t
def print_from_xy(xs):
x_min = min(v[1] for v in xs)
x_max = max(v[1] for v in xs)
y_min = min(v[0] for v in xs)
y_max = max(v[0] for v in xs)
for y in range(y_min, y_max + 1):
row = ""
for x in range(x_min, x_max + 1):
if (x, y) in xs:
row += "#"
else:
row += " "
print(row)
robots_speed = {}
robots = list()
for i, line in enumerate(data.splitlines()):
c, r, vc, vr = ints(line)
robots_speed[i] = (vr, vc)
robots.append((i, r, c))
for j in range(rows * cols):
nrobots = list()
count = 0
for i, r, c in robots:
vr, vc = robots_speed[i]
nr = (r + vr) % rows
nc = (c + vc) % cols
nrobots.append((i, nr, nc))
robots = nrobots
if j == 99:
print(score(robots))
robots_coords = [(r, c) for _, r, c in robots]
if len(robots_coords) == len(set(robots_coords)):
print(j + 1)
# print_from_xy(robots_coords)
break

92
2024/d15.py Normal file
View File

@@ -0,0 +1,92 @@
from lib import get_data
from lib import Grid2D
DIRS = {
"^": (-1, 0),
">": (0, 1),
"v": (1, 0),
"<": (0, -1),
}
data = get_data(__file__)
data, moves = data.split("\n\n")
g = Grid2D(data)
def simulate(grid: Grid2D, moves: str):
(p,) = g.find("@")
for c in moves:
if c == "\n":
continue
d = DIRS[c]
to_move = set([p])
row = to_move
blocked = False
while True:
nrow = set()
for (
r,
c,
) in row:
np = r + d[0], c + d[1]
if g[np] == "#":
blocked = True
elif g[np] == "O":
nrow.add(np)
elif g[np] == "[":
nrow.add(np)
if d == (1, 0) or d == (-1, 0):
nrow.add((np[0], np[1] + 1))
elif g[np] == "]":
if d == (1, 0) or d == (-1, 0):
nrow.add((np[0], np[1] - 1))
nrow.add(np)
to_move |= row
row = nrow
if len(row) == 0 or blocked:
break
if not blocked:
to_place = {}
p = p[0] + d[0], p[1] + d[1]
for tm in to_move:
np = tm[0] + d[0], tm[1] + d[1]
to_place[np] = g[tm]
g[tm] = "."
for pos, c in to_place.items():
g[pos] = c
def score(g: Grid2D) -> int:
os = g.find("O")
if os:
return sum(r * 100 + c for r, c in os)
return sum(r * 100 + c for r, c in g.find("["))
simulate(g, moves)
print(score(g))
ndata = ""
for c in data:
if c == "#":
ndata += "##"
elif c == "O":
ndata += "[]"
elif c == ".":
ndata += ".."
elif c == "@":
ndata += "@."
else:
ndata += c
g = Grid2D(ndata)
(p,) = g.find("@")
simulate(g, moves)
print(score(g))
exit()

96
2024/d16.py Normal file
View File

@@ -0,0 +1,96 @@
import heapq
from collections import defaultdict
from lib import get_data
from lib import Grid2D
data = get_data(__file__)
g = Grid2D(data)
def search(start, end, dir):
paths = set()
def reconstruct(parents, current):
seen = set()
to_visit = [current]
while to_visit:
current = to_visit.pop()
if current in seen:
continue
seen.add(current)
paths.add(current[0])
for p in parents[current]:
to_visit.append(p)
def dist(n1, n2):
"""cost from node to node"""
if n1 == 0 or n1 == n2:
return 0
p1, d1 = n1
p2, d2 = n2
if p1 != p2:
return 1
if d1 != d2:
return 1000
assert False
def h(node):
"""heuristic function (never overestimate)"""
pos = node[0]
return abs(pos[0] - end[0]) + abs(pos[1] - end[1])
def is_goal(node):
return node[0] == end
def neighbors(node):
pos, dir = node
npos = pos[0] + dir[0], pos[1] + dir[1]
nbs = []
if g.contains(npos) and g[npos] != "#":
nbs.append((npos, dir))
ndir = dir[1], -dir[0]
nbs.append((pos, ndir))
ndir = -dir[1], dir[0]
nbs.append((pos, ndir))
return nbs
starts = [(start, dir)]
open_set = []
g_score = {}
cost = None
for start in starts:
heapq.heappush(open_set, (h(start), start))
g_score[start] = dist(0, start)
parents = defaultdict(list)
while open_set:
current_f_score, current = heapq.heappop(open_set)
if is_goal(current):
# assert current_f_score == g_score[current]
gs = g_score[current]
if cost is None or gs <= cost:
cost = gs
reconstruct(parents, current)
else:
break
for neighbor in neighbors(current):
tentative_g_score = g_score[current] + dist(current, neighbor)
if tentative_g_score <= g_score.get(neighbor, 10**12):
parents[neighbor].append(current)
g_score[neighbor] = tentative_g_score
f_score = g_score[neighbor] + h(neighbor)
heapq.heappush(open_set, (f_score, neighbor))
return cost, paths
(start,) = g.find("S")
(end,) = g.find("E")
dir = (0, 1)
cost, paths = search(start, end, dir)
print(cost)
print(len(paths))

61
2024/d17.py Normal file
View File

@@ -0,0 +1,61 @@
from lib import get_data
from lib import ints
data = get_data(__file__)
nums, prog = data.split("\n\n")
regs = ints(nums)
prog = ints(prog)
i = 0
out = []
while i < len(prog) - 1:
inst, op = prog[i], prog[i + 1]
assert op != 7
combo = op if op not in [4, 5, 6] else regs[op - 4]
literal = op
match inst:
case 0:
regs[0] = regs[0] >> combo
case 1:
regs[1] = regs[1] ^ literal
case 2:
regs[1] = combo % 8
case 3:
if regs[0] != 0:
i = literal - 2
case 4:
regs[1] = regs[1] ^ regs[2]
case 5:
out.append(combo % 8)
case 6:
regs[1] = regs[0] // (2**combo)
case 7:
regs[2] = regs[0] // (2**combo)
case _:
assert False
i += 2
print(",".join(map(str, out)))
def get_a(a_current, xs):
if len(xs) == 0:
return [a_current // 8]
x = xs[0]
aan = []
for a_new in range(8):
a = a_current + a_new
b = a % 8
assert b == a_new
b = b ^ 1
c = a >> b
b = b ^ c
b = b ^ 5
if (b % 8) == x:
aan += get_a(a << 3, xs[1:])
return aan
aa = get_a(0, list(reversed(prog)))
print(min(aa))

67
2024/d18.py Normal file
View File

@@ -0,0 +1,67 @@
import heapq
from lib import get_data
from lib import ints
from lib import Grid2D
data = get_data(__file__)
size = 71
grid = "\n".join(["." * size for _ in range(size)])
g = Grid2D(grid)
for i, line in enumerate(data.splitlines()):
x, y = ints(line)
g[(y, x)] = "x"
start = (0, 0)
end = (size - 1, size - 1)
def dist(n1, n2):
"""cost from node to node"""
if n1 == 0 or n1 == n2:
return 0
return 1
def h(node):
"""heuristic function (never overestimate)"""
return abs(node[0] - end[0]) + abs(node[1] - end[1])
def is_goal(node):
return node == end
def neighbors(node):
nbs = []
for nb in g.neighbors_ort(node):
if g[nb] != "x":
nbs.append(nb)
return nbs
starts = [start]
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))
if i == 1023:
print(cost)
if cost is None:
print(line)
break

30
2024/d19.py Normal file
View File

@@ -0,0 +1,30 @@
from lib import get_data
from functools import cache
data = get_data(__file__)
lines = data.splitlines()
patterns = set(lines[0].split(", "))
@cache
def valid(line):
if line == "":
return 1
r = 0
for j in range(1, len(line) + 1):
sub = line[:j]
if sub in patterns:
r += valid(line[j:])
return r
p1, p2 = 0, 0
lines = lines[2:]
for line in lines:
v = valid(line.strip())
if v > 0:
p1 += 1
p2 += v
print(p1)
print(p2)

90
2024/d20.py Normal file
View File

@@ -0,0 +1,90 @@
from collections import deque
from lib import get_data
from lib import Grid2D
data = """###############
#...#...#.....#
#.#.#.#.#.###.#
#S#...#.#.#...#
#######.#.#.###
#######.#.#...#
#######.#.###.#
###..E#...#...#
###.#######.###
#...###...#...#
#.#####.#.###.#
#.#...#.#.#...#
#.#.#.#.#.#.###
#...#...#...###
###############
"""
data = get_data(__file__)
g = Grid2D(data)
(start,) = g.find("S")
(end,) = g.find("E")
cost = 0
sd = dict()
de = dict()
queue = deque([(start[0], start[1], 0)])
seen = set()
while queue:
r, c, cost = queue.popleft()
if (r, c) in sd:
continue
sd[(r, c)] = cost
if (r, c) == end:
break
for r, c in g.neighbors_ort((r, c)):
if g[(r, c)] != "#":
queue.append((r, c, cost + 1))
seen = set()
queue = deque([(end[0], end[1], 0)])
while queue:
r, c, cost = queue.popleft()
if (r, c) in de:
continue
de[(r, c)] = cost
if (r, c) == start:
break
for r, c in g.neighbors_ort((r, c)):
if g[(r, c)] != "#":
queue.append((r, c, cost + 1))
for max_steps in [2, 20]:
cheats = set()
for cr, cc in g.find("S."):
start = cr, cc, 0
seen = set()
queue = deque([start])
while queue:
r, c, steps = queue.popleft()
if steps > max_steps:
continue
if (r, c) in seen:
continue
seen.add((r, c))
if g[(r, c)] != "#":
ncost = sd[(cr, cc)] + steps + de[(r, c)]
if cost - ncost >= 100:
cheats.add((cr, cc, r, c))
for dr, dc in [(-1, 0), (0, 1), (1, 0), (0, -1)]:
nr, nc = r + dr, c + dc
if g.contains((nr, nc)):
queue.append((nr, nc, steps + 1))
print(len(cheats))

87
2024/d21.py Normal file
View File

@@ -0,0 +1,87 @@
from lib import get_data
from collections import deque
from functools import cache
data = get_data(__file__)
# data = """029A
# 980A
# 179A
# 456A
# 379A"""
def new_dir(current, move):
return {
"<": {">": "v"},
"^": {">": "A", "v": "v"},
"v": {"<": "<", ">": ">", "^": "^"},
">": {"^": "A", "<": "v"},
"A": {"<": "^", "v": ">"},
}[current].get(move, None)
def new_num(current, move):
return {
"7": {">": "8", "v": "4", },
"8": {"<": "7", ">": "9", "v": "5"},
"9": {"<": "8", "v": "6"},
"4": {"^": "7", ">": "5", "v": "1"},
"5": {"^": "8", ">": "6", "<": "4", "v": "2"},
"6": {"^": "9", "<": "5", "v": "3"},
"1": {"^": "4", ">": "2"},
"2": {"^": "5", ">": "3", "<": "1", "v": "0"},
"3": {"^": "6", "<": "2", "v": "A"},
"0": {"^": "2", ">": "A"},
"A": {"^": "3", "<": "0"},
}[current].get(move, None)
@cache
def find(code, depth, lookup):
if depth == 0:
return len(code)
code = list(code)
t = 0
current = "A"
while code:
solutions = []
target = code.pop(0)
start = (current, "")
seen = set()
queue = deque([start])
while queue:
node = queue.popleft()
if node in seen:
continue
seen.add(node)
if node[0] == target:
solution = node[1] + "A"
if len(solutions) == 0:
solutions.append(solution)
elif len(solutions[-1]) == len(solution):
solutions.append(solution)
else:
break
continue
for c in "<>v^":
next = lookup(node[0], c)
if next is not None:
queue.append((next, node[1] + c))
current = target
t += min(find(code, depth - 1, new_dir) for code in solutions)
return t
p1, p2 = 0, 0
for line in data.splitlines():
i = int("".join([c for c in line if c.isdigit()]))
s1 = find(line, 3, new_num)
s2 = find(line, 26, new_num)
p1 += s1 * i
p2 += s2 * i
print(p1)
print(p2)

56
2024/d22.py Normal file
View File

@@ -0,0 +1,56 @@
from lib import get_data
from lib import ints
from collections import defaultdict
data = get_data(__file__)
def secret(x):
x ^= x * 64
x %= 16777216
x ^= x // 32
x %= 16777216
x ^= x * 2048
x %= 16777216
return x
def slice(xs, n):
return [slice for slice in zip(*[xs[i:] for i in range(n)])]
codes = defaultdict(int)
def int_seq(x):
xs = []
for _ in range(2000):
xs.append(x % 10)
x = secret(x)
ds = [a - b for a, b in zip(xs[1:], xs)]
seen = set()
for s in slice(list(zip(xs[1:], ds)), 4):
code = tuple([e[1] for e in s])
v = s[-1][0]
if not code in seen:
codes[code] += v
seen.add(code)
for line in data.splitlines():
(x,) = ints(line)
s = 0
for line in data.splitlines():
(x,) = ints(line)
int_seq(x)
for _ in range(2000):
x = secret(x)
s += x
print(s)
print(max(codes.values()))

43
2024/d23.py Normal file
View File

@@ -0,0 +1,43 @@
from lib import get_data
from collections import defaultdict
data = get_data(__file__)
g = defaultdict(set)
for line in data.splitlines():
a, b = line.split("-")
g[a].add(b)
g[b].add(a)
cs = set()
for a, bs in g.items():
for b in bs:
for c in g[b]:
if c in bs:
cs.add(tuple(sorted([a, b, c])))
cs = [t for t in cs if any(x.startswith("t") for x in t)]
print(len(cs))
def find_largest_clique(graph):
def bron_kerbosch(r, p, x, max_clique):
if not p and not x:
if len(r) > len(max_clique[0]):
max_clique[0] = r.copy()
return
for v in p.copy():
neighbors = graph[v]
bron_kerbosch(r | {v}, p & neighbors, x & neighbors, max_clique)
p.remove(v)
x.add(v)
max_clique = [set()]
bron_kerbosch(set(), set(graph.keys()), set(), max_clique)
return max_clique[0]
cs = find_largest_clique(g)
print(",".join(sorted(cs)))

143
2024/d24.py Normal file
View File

@@ -0,0 +1,143 @@
from lib import get_data
from lib import ints
LHS, OP, RHS, _, OUT = 0, 1, 2, 3, 4
def run(states, gates):
change = True
while change:
change = False
for gate in gates:
i1, op, i2, _, out = gate
if i1 in states and i2 in states and out not in states:
change = True
outv = None
match op:
case "OR":
outv = states[i1] | states[i2]
case "XOR":
outv = states[i1] ^ states[i2]
case "AND":
outv = states[i1] & states[i2]
assert outv is not None
states[out] = outv
i, t = 0, 0
while True:
si = f"z{i:02}"
if not si in states:
break
if states[si]:
t += 2**i
i += 1
return t
data = get_data(__file__)
s1, s2 = data.split("\n\n")
states = {}
for line in s1.splitlines():
gate, value = line.split(": ")
states[gate] = int(value)
gates = []
out_to_index = {}
xors = {}
zz = []
for i, line in enumerate(s2.splitlines()):
o1, op, o2, _, out = line.split()
o1, o2 = sorted([o1, o2])
gate = [o1, op, o2, "->", out]
gates.append(gate)
out_to_index[out] = i
if out.startswith("z"):
zz.append(out)
if op == "XOR" and o1.startswith("x"):
(index,) = ints(o1)
xors[index] = gate
p1 = run(states, gates)
print(p1)
NUMBER_Z_GATES = ints(sorted(zz)[-1])[0] + 1
# print(NUMBER_Z_GATES)
# pprint(xors)
xx = [f"x{i:02}" for i in range(NUMBER_Z_GATES - 1)]
yy = [f"y{i:02}" for i in range(NUMBER_Z_GATES - 1)]
# def resolve(x):
# if x in xx or x in yy:
# return f"{x}"
# # return x
# a, op, b = gate_to_in[x]
# aa = resolve(a)
# bb = resolve(b)
# # print(aa, bb)
# aa, bb = sorted([aa, bb], reverse=True)
# op = "^" if op == "XOR" else ("|" if op == "OR" else "&")
# return f"({aa} {op} {bb})"
# # return [aa, op, bb]
def find_xor(index):
xor = xors[index][OUT]
for gate in gates:
if gate[OP] == "XOR" and (gate[LHS] == xor or gate[RHS] == xor):
return gate
return None
corrected = []
for i in range(NUMBER_Z_GATES):
sx, sy, sz = [f"{c}{i:02}" for c in "xyz"]
gate = gates[out_to_index[sz]]
if i == 0:
assert gate == xors[i]
elif i == NUMBER_Z_GATES - 1:
pass # last gate needs special check
else:
fixed = False
xor = xors[i]
if gate[OP] != "XOR":
cgate = find_xor(i)
assert cgate is not None
gate[OUT], cgate[OUT] = cgate[OUT], gate[OUT]
corrected.append(gate[OUT])
corrected.append(cgate[OUT])
out_to_index[gate[OUT]], out_to_index[cgate[OUT]] = (
out_to_index[cgate[OUT]],
out_to_index[gate[OUT]],
)
fixed = True
# print(f"[swap] {gate} <-> {cgate}")
gate = gates[out_to_index[sz]]
assert gate[OP] == "XOR"
# print(gate)
i_lhs, i_rhs = out_to_index[gate[LHS]], out_to_index[gate[RHS]]
a, b = gates[i_lhs], gates[i_rhs]
if a == xor:
# print(f"[ok] {sz} ({fixed})")
pass
elif b == xor:
# print(f"[ok] {sz} ({fixed})")
gate[0], gate[2] = gate[2], gate[0]
else:
if a[OP] == "AND":
a[OUT], xor[OUT] = xor[OUT], a[OUT]
corrected.append(a[OUT])
corrected.append(xor[OUT])
# print(f"[swap] {a} <-> {xor}")
out_to_index[a[OUT]], out_to_index[xor[OUT]] = (
out_to_index[xor[OUT]],
out_to_index[a[OUT]],
)
# print(f"[ok] {sz} (manual fix)")
else:
print(f"[nok] {sz} (don't know how to fix)")
print(",".join(sorted(corrected)))

25
2024/d25.py Normal file
View File

@@ -0,0 +1,25 @@
from lib import get_data
from lib import Grid2D
data = get_data(__file__)
locks = []
keys = []
for o in data.split("\n\n"):
g = Grid2D(o)
if all(g[(0, c)] == "#" for c in range(g.n_cols)):
keys.append(g)
elif all(g[g.n_rows - 1, c] == "#" for c in range(g.n_cols)):
locks.append(g)
else:
assert False
t = 0
for key in keys:
for lock in locks:
k = key.find("#")
l = lock.find("#")
if len(k) + len(l) == len(set(k) | set(l)):
t += 1
print(t)

1
2024/lib.py Symbolic link
View File

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

22
2025/d01.py Normal file
View File

@@ -0,0 +1,22 @@
from lib import get_data
NUM = 100
data = get_data(__file__)
pos = 50
r1, r2 = 0, 0
for line in data.splitlines():
d = line[0]
n = int(line[1:])
dir = 1 if d == "R" else -1
for _ in range(n):
pos = (pos + dir) % NUM
if pos == 0:
r2 += 1
if pos == 0:
r1 += 1
print(r1)
print(r2)

34
2025/d02.py Normal file
View File

@@ -0,0 +1,34 @@
from lib import get_data
data = get_data(__file__)
def is_invalid(xs: str) -> bool:
for seq_len in range(1, len(xs) // 2 + 1):
if xs[:seq_len] == xs[seq_len:]:
return True
return False
def is_invalid2(xs: str) -> bool:
for seq_len in range(1, len(xs)):
if len(xs) % seq_len != 0:
continue
for repeat in range(len(xs) // seq_len):
i = seq_len * repeat
if xs[:seq_len] != xs[i:i + seq_len]:
break
else:
return True
return False
r1, r2 = 0, 0
for id_range in data.split(","):
lo, up = list(map(int, id_range.split("-")))
for id in range(lo, up + 1):
if is_invalid(str(id)):
r1 += id
if is_invalid2(str(id)):
r2 += id
print(r1)
print(r2)

34
2025/d03.py Normal file
View File

@@ -0,0 +1,34 @@
from lib import get_data
data = get_data(__file__)
def max_joltage(xs: str, target_len: int) -> int:
def dynamic(xs: str) -> dict[int, str]:
if len(xs) == 0:
return {0: ""}
rs = dynamic(xs[1:])
for i in range(target_len, 0, -1):
if not (i - 1) in rs:
continue
nv = int(xs[0] + rs[i - 1])
if not i in rs:
rs[i] = str(nv)
elif nv > int(rs[i]):
rs[i] = str(nv)
return rs
rs = dynamic(xs)
return int(rs[target_len])
r1 = 0
r2 = 0
for line in data.splitlines():
r1 += max_joltage(line, 2)
r2 += max_joltage(line, 12)
print(r1)
print(r2)

32
2025/d04.py Normal file
View File

@@ -0,0 +1,32 @@
from lib import get_data, Grid2D
data = get_data(__file__)
g = Grid2D(data)
r = 0
adjs = []
for pos in g.all_coords():
if g[pos] != "@":
continue
nr_of_rolls_adj = len([nb for nb in g.neighbors_adj(pos) if g[nb] == "@"])
if nr_of_rolls_adj < 4:
r += 1
adjs.append(pos)
print(r)
r = 0
while True:
adjs = []
for pos in g.all_coords():
if g[pos] != "@":
continue
nr_of_rolls_adj = len([nb for nb in g.neighbors_adj(pos) if g[nb] == "@"])
if nr_of_rolls_adj < 4:
adjs.append(pos)
if adjs:
r += len(adjs)
for a in adjs:
g[a] = "."
else:
break
print(r)

45
2025/d05.py Normal file
View File

@@ -0,0 +1,45 @@
from lib import get_data
data = get_data(__file__)
ranges, ids = data.split("\n\n")
ids = tuple(map(int, ids.splitlines()))
ranges = [list(map(int, line.split("-"))) for line in ranges.splitlines()]
updated = True
while updated:
updated = False
sorted_ranges = []
for lo, up in ranges:
assert lo <= up
for i in range(len(sorted_ranges)):
slo, sup = sorted_ranges[i]
if up + 1 < slo:
sorted_ranges.insert(i, (lo, up))
elif lo < slo:
sorted_ranges[i] = (lo, max(up, sup))
updated = True
elif lo <= sup:
sorted_ranges[i] = (slo, max(up, sup))
updated = True
else:
continue
break
else:
sorted_ranges.append((lo, up))
ranges = sorted_ranges
result_part_1 = 0
for id_to_check in ids:
for lo, up in ranges:
if lo <= id_to_check <= up:
result_part_1 += 1
break
print(result_part_1)
result_part_2 = 0
for lo, up in ranges:
result_part_2 += up - lo + 1
print(result_part_2)
assert result_part_2 == 350780324308385

60
2025/d06.py Normal file
View File

@@ -0,0 +1,60 @@
from lib import get_data
from typing import TypeVar
T = TypeVar('T')
data = """123 328 51 64
45 64 387 23
6 98 215 314
* + * + """
data = get_data(__file__)
def mul(xs):
r = 1
for x in xs:
r *= x
return r
lines = zip(*[line.split() for line in data.splitlines()])
r = 0
for xs in lines:
xs, op = xs[:-1], xs[-1]
xs = list(map(int, xs))
if op == "+":
r += sum(xs)
elif op == "*":
r += mul(xs)
else:
assert False, "Unexpected op"
print(r)
def split(xs: list[T], delim: T) -> list[list[T]]:
res = [[]]
for x in xs:
if x == delim:
res.append([])
else:
res[-1].append(x)
return res
chars_trans: list[tuple[str]] = list(zip(*[line for line in data.splitlines()]))
lines_trans: list[str] = list(map(lambda line: "".join(line).strip(), chars_trans))
cols: list[list[str]] = split(lines_trans, '')
r2 = 0
for xs in cols:
x0 = xs[0]
acc, op = int(x0[:-1]), x0[-1]
for x in xs[1:]:
x = int(x)
if op == "+":
acc += x
elif op == "*":
acc *= x
else:
assert False, "Unexpected op"
r2 += acc
print(r2)

59
2025/d07.py Normal file
View File

@@ -0,0 +1,59 @@
from lib import get_data, Grid2D
from collections import defaultdict
data = """.......S.......
...............
.......^.......
...............
......^.^......
...............
.....^.^.^.....
...............
....^.^...^....
...............
...^.^...^.^...
...............
..^...^.....^..
...............
.^.^.^.^.^...^.
..............."""
data = get_data(__file__)
g = Grid2D(data)
beams = [g.find('S')[0][1]]
splits = 0
for row in range(1, g.n_rows - 1):
new_beams = set()
for beam in beams:
field = g[(row, beam)]
if field == "^":
splits += 1
new_beams.add(beam - 1)
new_beams.add(beam + 1)
elif field == ".":
new_beams.add(beam)
else:
assert False, "unexpected field"
beams = list(new_beams)
# print(beams)
print(splits)
beams = {g.find('S')[0][1]: 1}
for row in range(1, g.n_rows - 1):
new_beams = defaultdict(int)
for beam, count in beams.items():
field = g[(row, beam)]
if field == "^":
new_beams[beam - 1] += count
new_beams[beam + 1] += count
elif field == ".":
new_beams[beam] += count
else:
assert False, "unexpected field"
beams = new_beams
print(sum(beams.values()))

1
2025/lib.py Symbolic link
View File

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

496
README.md
View File

@@ -2,211 +2,60 @@
Solutions and utility script for Advent of Code challenges in Python.
## AoC 2015
## AoC 2025
- Day 1: 3:10
- Day 2: 5:24 :/
- Day 3: 7:26 ... :/
- Day 4: 5:11 ...
- Day 5: 14:05 o.O
- Day 6: 15:00
- Day 7: 17:00
- Day 8: 14:55
- Day 9: 13:48
- Day 10: 70:00 ... slow, but fun
- Day 11: 12:30
- Day 12: 6:03
- Day 13: 7:06 (would have been first by a minute, probably 70ish in 2023)
- Day 14: 27:45 ... that was weak, logic error for part 2 :/
- Day 15: 16:00 I should probably stop brute forcing these optimization problems
- Day 16: 19:00
- Day 17: 9:05
- Day 18: 10:39
- Day 19: Many days... yeah this one took me way too long to figure out
- Day 20: 10:54
- Day 21: 25:52 cute bug where I didn't consider that no armor is an option
- Day 22: That was bad. Did not know how to choose between dfs/bfs and logic errors.
- Day 23: 10:00
- Day 24: 20:00 ugly - recursive solution would be more elegant
- Day 25: 9:34
Only twelve problems and no leaderboard this year. That means life will be less
stressful and this will actually be more fun. Thank you Eric Wastl and let's go!
## AoC 2016
- Day 1: Maybe the hardest day 1 part 2 ever for me? I struggled to calculate
the number of ticks on 0 directly and ended up just iterating over all the
ticks naively. Maybe I should revisit this.
- Day 2: The simple direct approach with iterating over all the IDs in the
ranges just worked okay. Not pretty but acceptable.
- Day 3: Fun dynamic programming problem. Part 2 took me a little too long
overall but it was fun.
- Day 4: One of the easier grid based puzzles. Less than five minutes with
existing grid library.
- Day 5: A problem with ranges; I decided to implement proper range merging this
time because I always kind of avoid that. I could probably decrease the
complexity from O(n^2) to O(n log(n)) but I am happy that I've implemented
merging at all.
- Day 6: Transposing some rows and cols. Fun and good to keep the brain fit
but not really hard. I would have been way too slow for the leaderboard
in pre-AI years.
- Day 7: Grid puzzle that required non-naiv implementation for part 2. Took
me a second to realize that I could not save the coordinates by list but
had to use a count to be efficient.
- Day 8:
- Day 1: 29:00 That was emberassingly slow. Out of my rhythm?
- Day 2: 13:24 Getting back into it but still slow af, obviously.
- Day 3: 11:20 Ugly and slow.
- Day 4: 21:05 -__-
- Day 5: 29:35 -___-
- Day 6: 4:20 okay
- Day 7: 31:20 hmm
- Day 8: 14:50 meh
- Day 9: 26:00 okay
- Day 10: 23:07 okay
- Day 11: 75:00 -__-
- Day 12: 10:05 okay
- Day 13: 9:43 okayish
- Day 14: 120:00 struggled with this one (example incorrect?)
- Day 15: Trial and error. Should use CRT instead.
- Day 16: 14:11
- Day 17: 45:00 didn't follow instructions... focus!
- Day 18: 9:43
- Day 19: 90:00 that wasn't easy for me
- Day 20: 67:00
- Day 21: 32:33
- Day 22: 90:00
- Day 23: 60:00
- Day 24: 48:00
- Day 25: 6:45
## AoC 2017
## AoC 2024
- Day 1: 7:30
- Day 2: 6:15
- Day 3: 75:00 hmm should have been way quicker
- Day 4: 3:02
- Day 5: 6:13
- Day 6: 8:37
- Day 7: 19:22
- Day 8: 8:15
- Day 9: 6:10
- Day 10: 55:00
- Day 11: 66:06
- Day 12: 6:44
- Day 13: 120:00
- Day 14: 17:48
- Day 15: 11:40
- Day 16: 13:16
- Day 17: 55:00
- Day 18: 23:00
- Day 19: 45:00
- Day 20: 9:55 (would have been 10th)
- Day 21: 90:00
- Day 22: 25:00
- Day 23: Multiple days... but super fun.
- Day 24: 15:45 (48th)
- Day 25: 41:00
## AoC 2018
- Day 1: 1:49 (2nd)
- Day 2: 10:53
- Day 3: 6:16 (24th)
- Day 4: 25:16
- Day 5: 17:03
- Day 6: 1:10:29
- Day 7: 20:15
- Day 8: 18:35
- Day 9: 1:17:52
- Day 10: 19:14
- Day 11: 22:52
- Day 12: 180:00
- Day 13: 73:09
- Day 14: 20:48
- Day 15: 185:11
- Day 16: 36:10
- Day 17: 180:00
- Day 18: 24:04
- Day 19: days, super fun, but hard for me
- Day 20:
- Day 21: 28:40 (16th - brute force but still not so bad)
- Day 22: 185:00 (should not have been so slow but was fun)
- Day 23:
## AoC 2019
- Day 1: 4:25 (copy and paste error, no leaderboard)
- Day 2: 7:07 (19th)
- Day 3: 10:46 (37th)
- Day 4: 8:48 (just too slow, no leaderboard)
- Day 5: 34:24 (that wasn't hard at all)
- Day 6: 23:17 (so slow, no leaderboard)
- Day 7: 72:17 (I found that one challenging, 30:33 would have been required for leaderboard)
- Day 8: 08:55 (54th)
- Day 9: 115:00 (Try to read next time.)
- Day 10: 76:00 (This wasn't easy for me. Fun, though.)
- Day 11: 21:04 (Too slow, but fun.)
- Day 12: days (Took me a while to get the right idea.)
- Day 13: >120:00 (Just struggling so much for some reason.)
- Day 14: >120:00 (Hmm, wasn't that hard either.)
- Day 15: >120:00 (I am really weak at the moment.)
- Day 16: days (Wow. Just too hard for me to solve quickly?)
- Day 17: days (Fun but too tricky for me to be fast.)
- Day 18: days (Slow and slow algorithm.)
- Day 19: 40:00 (Way too slow! Oversight error. Come on.)
- Day 20: days (Not actually that hard but I struggled for no reason.)
- Day 21: days (But it was super fun!)
- Day 22:
## AoC 2020
- Day 1: 2:48 (people weren't able to submit because of a website outage)
- Day 2: 4:47 (no leaderboard, you can tell it's getting faster)
- Day 3: 7:06 (way too slow, lol; time to take it seriously)
- Day 4: 14:30 (yo, I am just too slow)
- Day 5: 11:53 (not competitive)
- Day 6: 4:11 (75th, finally on the leaderboard)
- Day 7: 24:39 (bad)
- Day 8: 6:26 (43th, tied George Hotz :)
- Day 9: 7:37 (choked bad)
- Day 10: 34:27 (so weak)
- Day 11: 21:05 (hmmm, I rally have to analyze why I am so slow)
- Day 12: 21:52 (just slow again for an easy problem)
- Day 13: 18:00 (I don't really understand the CRT to be honest)
- Day 14: 40:26 (Made a bunch of mistakes even misunderstanding Python)
- Day 15: 17:57 (Too slow for an easy one like this)
- Day 16: 33:00 (Not too unhappy really.)
- Day 17: 10:00 (40th)
- Day 18: 80:00 (I am struggling with stuff where parsing is involved)
- Day 19: 78:00 (Should have been faster)
- Day 20: 95:00 (Lot's of code)
- Day 21: 23:02 (Simply to slow)
- Day 22: 45:49 (Simple and too slow)
- Day 23: 105:00 (Sad)
- Day 24: 15:38 (Close to leaderboard)
- Day 25: 14:40 (Way too slow)
## AoC 2022
Done with this. Overall everything is solvable. It's more about consistency
and focus. Of course, learning more algorithms and techniques helps.
**Times:**
- Day 1: 7:52 ... so slow brah :/ top 100 required 2:05...
- Day 2: 22:30 ... I mistyped the first and second was just bad top 100 would
have been 6:16 (doable?)
- Day 3: 13:08 actually decent but top 100 required 5:24
- Day 4: 7:08 but top 100 required 3:33 still okay
- Day 5: 11:56 but 7:58 for top 100... getting better?
- Day 6: 3:50 but 2:25 for leaderboard :D
- Day 7: 27:55 and 14:47 for leaderboard; okay, I would say
- Day 8: 61:00 and 10:00 for leaderboard; I need template code for searching
coordinate systems
- Day 9: 58:00 and 7:32 for leaderboard; I need code for 2D stuff
- Day 10: 25:20 and 12:17 for leaderboard; okay, okay
- Day 11: 45:00 and 18:00 for leaderboard; arg, it was doable man
- Day 12: 39:45 and 9:46 for leaderboard; the people are ready for their
searches :D
- Day 13: 44:00 and 12:56 for leaderboard; these people are just good,
seriously
- Day 14: 35:00 and 14:54; I just have to get that much quicker... 2D code!
- Day 15: 150:00 and 27:00; I didn't use Manhattan dist initially, lot's of
debugging
- Day 16: 52:00 and 64:00; ARE YOU SAYING I WOULD HAVE MADE THE
LEADERBOARD?!?!?!?!?!?!?!
- Day 17: Second one was fun with having to detect the repetition.
- Day 18: 12:00 and 32:00; really straightforward and of course way too slow.
- Day 19: Slow. (2024-09-13 improved with help from hyper neutrino.)
- Day 20: Struggled way too much.
- Day 21: Straightforward and relatively fast.
- Day 22: Very hard and wasn't able to do hands free. Even the best guys took
over an hour.
- Day 23: Super straightforward, but took me way longer than it should have
because I loose focus and make silly errors. Like back in Middleschool.
- Day 24: Easy. Should have been faster, but no crazy mistakes. Still way too
slow for something easy like this.
- Day 25: Quickly solved via constraint solver. Actual solution much simpler. Don't know
if I would have been able to figure it out.
- Day 1: `00:01:30 124 0 00:02:49 141 0`
- Day 2: `00:05:34 686 0 00:08:21 531 0`
- Day 3: `00:01:48 165 0 00:02:40 56 45`
- Day 4: `00:07:47 1101 0 00:24:07 2410 0`
- Day 5: `00:07:07 679 0 00:23:02 1998 0`
- Day 6: `00:09:43 1082 0 00:16:29 464 0`
- Day 7: `00:12:29 2058 0 00:13:08 1170 0`
- Day 8: `00:15:59 1742 0 00:26:56 2190 0`
- Day 9: `00:48:23 6055 0 01:09:38 3159 0`
- Day 10: `00:19:47 2976 0 00:20:47 2244 0`
- Day 11: `00:05:13 642 0 00:33:07 2673 0`
- Day 12: `00:30:30 3540 0 11:41:33 16212 0`
- Day 13: `00:05:35 225 0 00:55:48 2544 0`
- Day 14: `00:20:41 2136 0 00:35:14 1048 0`
- Day 15: ` >24h 32248 0 >24h 23671 0`
- Day 16: ` >24h 24941 0 >24h 20575 0`
- Day 17: `17:34:16 23722 0 >24h 17778 0`
- Day 18: `14:35:01 20398 0 14:37:18 19550 0`
- Day 19: `00:14:37 2001 0 00:19:43 1584 0`
- Day 20: `01:08:53 3637 0 01:53:01 2837 0`
- Day 21: `00:48:40 215 0 >24h 16427 0`
- Day 22: `00:13:04 1930 0 00:28:29 739 0`
- Day 23: ` >24h 20096 0 >24h 17620 0`
- Day 24: `15:57:01 17307 0 >24h 11326 0`
- Day 25: `10:41:54 14140 0 >24h 13631 0`
## AoC 2023
@@ -255,3 +104,250 @@ and focus. Of course, learning more algorithms and techniques helps.
nodes. I should probably try to write an algorith that does that, but meh.
Manually plotting requires matplotlib and networkx packages.
## AoC 2022
Done with this. Overall everything is solvable. It's more about consistency
and focus. Of course, learning more algorithms and techniques helps.
**Times:**
- Day 1: 7:52 ... so slow brah :/ top 100 required 2:05...
- Day 2: 22:30 ... I mistyped the first and second was just bad top 100 would
have been 6:16 (doable?)
- Day 3: 13:08 actually decent but top 100 required 5:24
- Day 4: 7:08 but top 100 required 3:33 still okay
- Day 5: 11:56 but 7:58 for top 100... getting better?
- Day 6: 3:50 but 2:25 for leaderboard :D
- Day 7: 27:55 and 14:47 for leaderboard; okay, I would say
- Day 8: 61:00 and 10:00 for leaderboard; I need template code for searching
coordinate systems
- Day 9: 58:00 and 7:32 for leaderboard; I need code for 2D stuff
- Day 10: 25:20 and 12:17 for leaderboard; okay, okay
- Day 11: 45:00 and 18:00 for leaderboard; arg, it was doable man
- Day 12: 39:45 and 9:46 for leaderboard; the people are ready for their
searches :D
- Day 13: 44:00 and 12:56 for leaderboard; these people are just good,
seriously
- Day 14: 35:00 and 14:54; I just have to get that much quicker... 2D code!
- Day 15: 150:00 and 27:00; I didn't use Manhattan dist initially, lot's of
debugging
- Day 16: 52:00 and 64:00; ARE YOU SAYING I WOULD HAVE MADE THE
LEADERBOARD?!?!?!?!?!?!?!
- Day 17: Second one was fun with having to detect the repetition.
- Day 18: 12:00 and 32:00; really straightforward and of course way too slow.
- Day 19: Slow. (2024-09-13 improved with help from hyper neutrino.)
- Day 20: Struggled way too much.
- Day 21: Straightforward and relatively fast.
- Day 22: Very hard and wasn't able to do hands free. Even the best guys took
over an hour.
- Day 23: Super straightforward, but took me way longer than it should have
because I loose focus and make silly errors. Like back in Middleschool.
- Day 24: Easy. Should have been faster, but no crazy mistakes. Still way too
slow for something easy like this.
- Day 25: Quickly solved via constraint solver. Actual solution much simpler. Don't know
if I would have been able to figure it out.
## 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: 162:00 (I couldn't figure out how to solve it as a tree so I did the basic way.)
- Day 19: days (Super hard for me but super fun ultimately once I had the right approach.)
- 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: 142:00 (Wonderful problem and hard for me but learned something new for sure.)
- Day 23: 81:38 (Fun but obviously not competitive.)
- Day 24: 232:00 (Super hard for me but I am proud of it.)
- Day 25: 15:43 (That could have been much much faster.)
## AoC 2020
- Day 1: 2:48 (people weren't able to submit because of a website outage)
- Day 2: 4:47 (no leaderboard, you can tell it's getting faster)
- Day 3: 7:06 (way too slow, lol; time to take it seriously)
- Day 4: 14:30 (yo, I am just too slow)
- Day 5: 11:53 (not competitive)
- Day 6: 4:11 (75th, finally on the leaderboard)
- Day 7: 24:39 (bad)
- Day 8: 6:26 (43th, tied George Hotz :)
- Day 9: 7:37 (choked bad)
- Day 10: 34:27 (so weak)
- Day 11: 21:05 (hmmm, I rally have to analyze why I am so slow)
- Day 12: 21:52 (just slow again for an easy problem)
- Day 13: 18:00 (I don't really understand the CRT to be honest)
- Day 14: 40:26 (Made a bunch of mistakes even misunderstanding Python)
- Day 15: 17:57 (Too slow for an easy one like this)
- Day 16: 33:00 (Not too unhappy really.)
- Day 17: 10:00 (40th)
- Day 18: 80:00 (I am struggling with stuff where parsing is involved)
- Day 19: 78:00 (Should have been faster)
- Day 20: 95:00 (Lot's of code)
- Day 21: 23:02 (Simply to slow)
- Day 22: 45:49 (Simple and too slow)
- Day 23: 105:00 (Sad)
- Day 24: 15:38 (Close to leaderboard)
- Day 25: 14:40 (Way too slow)
## AoC 2019
- Day 1: 4:25 (copy and paste error, no leaderboard)
- Day 2: 7:07 (19th)
- Day 3: 10:46 (37th)
- Day 4: 8:48 (just too slow, no leaderboard)
- Day 5: 34:24 (that wasn't hard at all)
- Day 6: 23:17 (so slow, no leaderboard)
- Day 7: 72:17 (I found that one challenging, 30:33 would have been required for leaderboard)
- Day 8: 08:55 (54th)
- Day 9: 115:00 (Try to read next time.)
- Day 10: 76:00 (This wasn't easy for me. Fun, though.)
- Day 11: 21:04 (Too slow, but fun.)
- Day 12: days (Took me a while to get the right idea.)
- Day 13: >120:00 (Just struggling so much for some reason.)
- Day 14: >120:00 (Hmm, wasn't that hard either.)
- Day 15: >120:00 (I am really weak at the moment.)
- Day 16: days (Wow. Just too hard for me to solve quickly?)
- Day 17: days (Fun but too tricky for me to be fast.)
- Day 18: days (Slow and slow algorithm.)
- Day 19: 40:00 (Way too slow! Oversight error. Come on.)
- Day 20: days (Not actually that hard but I struggled for no reason.)
- Day 21: days (But it was super fun!)
- Day 22: days (Needed some help...)
- Day 23: 23:13 (Still too slow even though my int computer was in good shape...)
- Day 24: 53:00 (Can I ever even get points at all?)
- Day 25: 70:00 (Well, done at least. Super super fun!)
## AoC 2018
- Day 1: 1:49 (2nd)
- Day 2: 10:53
- Day 3: 6:16 (24th)
- Day 4: 25:16
- Day 5: 17:03
- Day 6: 1:10:29
- Day 7: 20:15
- Day 8: 18:35
- Day 9: 1:17:52
- Day 10: 19:14
- Day 11: 22:52
- Day 12: 180:00
- Day 13: 73:09
- Day 14: 20:48
- Day 15: 185:11
- Day 16: 36:10
- Day 17: 180:00
- Day 18: 24:04
- Day 19: days (super fun, but hard for me)
- Day 20: weeks (a cache was all it took - weak)
- Day 21: 28:40 (16th - brute force but still not so bad)
- Day 22: 185:00 (should not have been so slow but was fun)
- Day 23: days
- Day 24: days
- Day 25: 29:20 (slow)
## AoC 2017
- Day 1: 7:30
- Day 2: 6:15
- Day 3: 75:00 hmm should have been way quicker
- Day 4: 3:02
- Day 5: 6:13
- Day 6: 8:37
- Day 7: 19:22
- Day 8: 8:15
- Day 9: 6:10
- Day 10: 55:00
- Day 11: 66:06
- Day 12: 6:44
- Day 13: 120:00
- Day 14: 17:48
- Day 15: 11:40
- Day 16: 13:16
- Day 17: 55:00
- Day 18: 23:00
- Day 19: 45:00
- Day 20: 9:55 (would have been 10th)
- Day 21: 90:00
- Day 22: 25:00
- Day 23: Multiple days... but super fun.
- Day 24: 15:45 (48th)
- Day 25: 41:00
## AoC 2016
- Day 1: 29:00 That was emberassingly slow. Out of my rhythm?
- Day 2: 13:24 Getting back into it but still slow af, obviously.
- Day 3: 11:20 Ugly and slow.
- Day 4: 21:05 -__-
- Day 5: 29:35 -___-
- Day 6: 4:20 okay
- Day 7: 31:20 hmm
- Day 8: 14:50 meh
- Day 9: 26:00 okay
- Day 10: 23:07 okay
- Day 11: 75:00 -__-
- Day 12: 10:05 okay
- Day 13: 9:43 okayish
- Day 14: 120:00 struggled with this one (example incorrect?)
- Day 15: Trial and error. Should use CRT instead.
- Day 16: 14:11
- Day 17: 45:00 didn't follow instructions... focus!
- Day 18: 9:43
- Day 19: 90:00 that wasn't easy for me
- Day 20: 67:00
- Day 21: 32:33
- Day 22: 90:00
- Day 23: 60:00
- Day 24: 48:00
- Day 25: 6:45
## AoC 2015
- Day 1: 3:10
- Day 2: 5:24 :/
- Day 3: 7:26 ... :/
- Day 4: 5:11 ...
- Day 5: 14:05 o.O
- Day 6: 15:00
- Day 7: 17:00
- Day 8: 14:55
- Day 9: 13:48
- Day 10: 70:00 ... slow, but fun
- Day 11: 12:30
- Day 12: 6:03
- Day 13: 7:06 (would have been first by a minute, probably 70ish in 2023)
- Day 14: 27:45 ... that was weak, logic error for part 2 :/
- Day 15: 16:00 I should probably stop brute forcing these optimization problems
- Day 16: 19:00
- Day 17: 9:05
- Day 18: 10:39
- Day 19: Many days... yeah this one took me way too long to figure out
- Day 20: 10:54
- Day 21: 25:52 cute bug where I didn't consider that no armor is an option
- Day 22: That was bad. Did not know how to choose between dfs/bfs and logic errors.
- Day 23: 10:00
- Day 24: 20:00 ugly - recursive solution would be more elegant
- Day 25: 9:34

39
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
@@ -226,7 +230,7 @@ def count_trailing_repeats(lst):
class A_Star(object):
def __init__(self, starts, is_goal, h, d, neighbors):
"""
:param h: heuristic function
:param h: heuristic function (never overestimate)
:param d: cost from node to node function
:param neighbors: neighbors function
"""
@@ -272,7 +276,7 @@ def extract_year_and_date(scriptname) -> tuple[str, str]:
def get_data(filename):
path, file = os.path.split(filename)
year = path[-4:]
day = file.replace("d", "").replace(".py", "")
day = file.replace("d0", "").replace("d", "").replace(".py", "")
txt_file = f"d{day}.txt"
if os.path.isfile(txt_file):
@@ -297,6 +301,35 @@ def mod_inverse(a, m):
g, x, _ = egcd(a, m)
if g != 1:
raise Exception('Modular inverse does not exist')
raise Exception("Modular inverse does not exist")
else:
return x % m
class V:
def __init__(self, *args):
self.xs: tuple[int] = tuple(args)
def __eq__(self, other):
if isinstance(other, V):
return all(v1 == v2 for v1, v2 in zip(self.xs, other.xs))
elif hasattr(other, "__len__") and len(self.xs) == len(other):
return all(v1 == v2 for v1, v2 in zip(self.xs, other))
return False
def __getitem__(self, i: int):
return self.xs[i]
def __hash__(self):
return hash(self.xs)
def __add__(self, other):
if isinstance(other, V):
return V(*[v1 + v2 for v1, v2 in zip(self.xs, other.xs)])
assert hasattr(other, "__len__"), f"V.__add__({self}, {other}) missing `len`"
assert len(self.xs) == len(other), f"V.__add__({self}, {other}) `len` mismatch"
return V(*[v1 + v2 for v1, v2 in zip(self.xs, other)])
def __repr__(self):
s = ", ".join(map(str, self.xs))
return f"V({s})"

10
pyproject.toml Normal file
View File

@@ -0,0 +1,10 @@
[project]
name = "aocpy"
version = "0.1.0"
description = "Make ruff and ty available via uv"
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
"ruff>=0.14.11",
"ty>=0.0.11",
]