Compare commits

...

84 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
7d1dc3f95e Solve 2020 day 24 and 25 and 2018 day 23 part 2 2024-10-08 19:36:45 -04:00
ea28a17ab9 Solve 2020 day 23 2024-10-06 18:50:29 -04:00
6475681cfb 2020 day 20-22 2024-10-04 18:38:32 -04:00
97b05614ee 2020 day 19 and day 20 part 1 2024-09-30 20:04:33 -04:00
36f20610ae 2019 day 22 part 1 2024-09-29 15:04:19 -04:00
695a4f7ddb 2020 day 18 2024-09-29 15:01:51 -04:00
25443fba48 2020 day 17 2024-09-26 21:36:37 -04:00
bb9e3321d7 2020 day 16 2024-09-25 21:13:18 -04:00
2eba36a29a 2019 day 21 2024-09-23 19:27:26 -04:00
25bd810886 2018 day 23 part 1 2024-09-22 13:49:51 -04:00
6c3aec3d6e Solve 2018 day 22 and 2019 day 20 2024-09-21 22:49:45 -04:00
db383c7cfa Solve 2020 day 15 2024-09-15 12:26:11 -04:00
7986ecfc21 Update template 2024-09-15 11:49:28 -04:00
0f7606410c Solve 2020 day 14 2024-09-15 11:46:41 -04:00
6e588bb928 Solve 2020 day 13 CRT jojo 2024-09-14 10:41:22 -04:00
4a72eeb348 Solve 2018 day 21, 2020 day 12 and improve 2022 day 19 2024-09-13 20:22:22 -04:00
9cd17279d9 2019 day 18 2024-09-10 18:58:58 -04:00
bf88f13791 Finally solve 2019 day 12 and others 2024-09-08 10:21:20 -04:00
fa6ea1dfbe 2019 day 16 complete 2024-09-02 14:32:35 -04:00
6cea472035 2019 day 17 complete 2024-09-02 10:32:35 -04:00
ad53396ca6 2020 day 9 2024-09-02 09:37:57 -04:00
b307e09f2b 2020 day 8 2024-09-02 09:27:04 -04:00
381f186a59 2018 day 20 wip 2024-09-02 09:24:55 -04:00
5fae7c0366 2018 day 20 wip 2024-09-01 15:39:21 -04:00
2647ccd353 Solve 2018 day 19 finally 2024-09-01 11:11:31 -04:00
9a30c8b88d 2020 day 6 and 7 2024-08-30 22:59:22 -04:00
305fe0b325 Solve 2020 day 5 2024-08-29 06:10:05 -04:00
121 changed files with 6880 additions and 757 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

@@ -2,40 +2,133 @@ from lib import get_data, str_to_ints
import d16
def part_1(data):
def print_regs(regs):
print(" ".join([f"regs[{i}]={regs[i]:10}" for i in range(len(regs))]) + " (ip)")
def print_inst(i, insts, ip):
op, arg1, arg2, dest = insts[i]
dest = f"regs[{dest}]" if dest != ip else "ip"
match op:
case "addi":
print(f"{i:2} {dest} = regs[{arg1}] + {arg2}")
case "addr":
print(f"{i:2} {dest} = regs[{arg1}] + regs[{arg2}]")
case "muli":
print(f"{i:2} {dest} = regs[{arg1}] * {arg2}")
case "mulr":
print(f"{i:2} {dest} = regs[{arg1}] * regs[{arg2}]")
case "seti":
print(f"{i:2} {dest} = {arg1}")
case "setr":
print(f"{i:2} {dest} = regs[{arg1}]")
case "eqrr":
print(f"{i:2} {dest} = (regs[{arg1}] == regs[{arg2}]) ? 1 : 0")
case "gtrr":
print(f"{i:2} {dest} = (regs[{arg1}] > regs[{arg2}]) ? 1 : 0")
case _:
print(f"{i:2} {dest} = {op}({arg1}, {arg2})")
def sum_of_divisors(n):
total = 1
for i in range(2, int(n**0.5) + 1):
if n % i == 0:
total += i
if i != n // i: # do not count square root twice
total += n // i
if n != 1:
total += n
return total
def run(data, break_after=None, reg_zero_init=0):
ip = None
regs = [0 for _ in range(6)]
regs[0] = 1 # part 2
regs[0] = reg_zero_init
insts = []
for line in data.splitlines():
if line.startswith("#"):
ip, = str_to_ints(line)
(ip,) = str_to_ints(line)
else:
fs = line.split()
vals = str_to_ints(line)
insts.append([fs[0]] + vals)
count = 0
assert ip is not None
while regs[ip] < len(insts):
if break_after is not None and count > break_after:
break
inst = insts[regs[ip]]
f = getattr(d16, inst[0])
# if inst[1:] == [3, 1, 3]:
# regs[3] = regs[1]
# regs[4] = regs[1]
f(regs, *inst[1:])
regs[ip] += 1
print(regs, regs[-1] + 1)
count += 1
return regs
def part_1(data):
regs = run(data)
print(regs[0])
def part_2(data):
regs = run(data, 10_000, 1)
# by analysing the code we can see that the int computer counts the sum of
# the number of divisors
print(sum_of_divisors(regs[1]))
def main():
data = get_data(__file__)
part_1(data)
part_2(data)
if __name__ == "__main__":
main()
listing = """
ip=5
0 ip = regs[5] + 16
1 regs[2] = 1
2 regs[4] = 1
3 regs[3] = regs[2] * regs[4] |
4 regs[3] = (regs[3] == regs[1]) ? 1 : 0 |
5 ip = regs[3] + regs[5] | jump to 7 if regs[3] == regs[1]
6 ip = regs[5] + 1 | skip 7
7 regs[0] = regs[2] + regs[0]
8 regs[4] = regs[4] + 1 | regs[4] += 1
9 regs[3] = (regs[4] > regs[1]) ? 1 : 0 |
10 ip = regs[5] + regs[3] | jump to 12 if regs[4] > regs[1]
11 ip = 2 | goto 3
12 regs[2] = regs[2] + 1 | regs[2] += 1
13 regs[3] = (regs[2] > regs[1]) ? 1 : 0 |
14 ip = regs[3] + regs[5] | jump to 16 if regs[2] > regs[1]
15 ip = 1 | goto 2
16 ip = regs[5] * regs[5]
17 regs[1] = regs[1] + 2
18 regs[1] = regs[1] * regs[1]
19 regs[1] = regs[5] * regs[1]
20 regs[1] = regs[1] * 11
21 regs[3] = regs[3] + 6
22 regs[3] = regs[3] * regs[5]
23 regs[3] = regs[3] + 15
24 regs[1] = regs[1] + regs[3]
25 ip = regs[5] + regs[0]
26 ip = 0
27 regs[3] = regs[5]
28 regs[3] = regs[3] * regs[5]
29 regs[3] = regs[5] + regs[3]
30 regs[3] = regs[5] * regs[3]
31 regs[3] = regs[3] * 14
32 regs[3] = regs[3] * regs[5]
33 regs[1] = regs[1] + regs[3]
34 regs[0] = 0
35 ip = 0
hypothesis: usm of numbers that divide the number in regs[0]
"""

94
2018/d20.py Normal file
View File

@@ -0,0 +1,94 @@
from lib import get_data, add2
from collections import defaultdict
DIRS = {
"N": (-1, 0),
"E": (0, 1),
"S": (1, 0),
"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()
c = (pos, i, tuple(i_outs))
if c in seen:
continue
else:
seen.add(c)
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
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
print(max(dists.values()))
print(len(over_thousand))

52
2018/d21.py Normal file
View File

@@ -0,0 +1,52 @@
from lib import get_data, str_to_ints
import d16
def run(data, break_after=None, reg_zero_init=0):
ip = None
regs = [0 for _ in range(6)]
regs[0] = reg_zero_init
insts = []
for line in data.splitlines():
if line.startswith("#"):
(ip,) = str_to_ints(line)
else:
fs = line.split()
vals = str_to_ints(line)
insts.append([fs[0]] + vals)
count = 0
assert ip is not None
seen = set()
last_added = None
while regs[ip] < len(insts):
if break_after is not None and count > break_after:
break
if regs[ip] == 28:
r4 = regs[4]
if len(seen) == 0:
print(r4)
if r4 in seen:
print(last_added)
break
else:
seen.add(r4)
last_added = r4
inst = insts[regs[ip]]
f = getattr(d16, inst[0])
f(regs, *inst[1:])
regs[ip] += 1
count += 1
def main():
data = get_data(__file__)
run(data, reg_zero_init=333)
if __name__ == "__main__":
main()

89
2018/d22.py Normal file
View File

@@ -0,0 +1,89 @@
from lib import get_data, str_to_ints, A_Star
data = get_data(__file__)
depth, x_target, y_target = str_to_ints(data)
# depth, x_target, y_target = 510, 10, 10
# depth, x_target, y_target = 9171, 7, 721
# print(depth, x_target, y_target)
x_area_max = x_target * 10
y_area_max = y_target * 10
maze = [[None for _ in range(x_area_max + 1)] for _ in range(y_area_max + 1)]
maze[0][0] = 0
maze[y_target][x_target] = 0
mod = 20183
for x in range(x_area_max + 1):
maze[0][x] = x * 16807 % mod
for y in range(y_area_max + 1):
maze[y][0] = y * 48271 % mod
for y in range(1, y_area_max + 1):
for x in range(1, x_area_max + 1):
if x == x_target and y == y_target:
continue
assert maze[y][x] is None
geo_index = ((maze[y][x - 1] + depth) * (maze[y - 1][x] + depth)) % mod
maze[y][x] = geo_index
t = 0
for y in range(y_target + 1):
for x in range(x_target + 1):
t += ((maze[y][x] + depth) % mod) % 3
print(t)
for y in range(y_area_max + 1):
for x in range(x_area_max + 1):
maze[y][x] = ((maze[y][x] + depth) % mod) % 3
def allowed(area, tool):
# 0 = rocky, 1 = wet, 2 = narrow
# 0 = torch, 1 = climbing, 2 = neither
return (
(area == 0 and (tool in [0, 1]))
or (area == 1 and (tool in [1, 2]))
or (area == 2 and (tool in [0, 2]))
)
def neighbors(state):
x, y, tool = state
r = []
area = maze[y][x]
for t in range(3):
if allowed(area, t) and t != tool:
r.append((x, y, t))
for dx, dy in [(0, 1), (0, -1), (1, 0), (-1, 0)]:
nx, ny = x + dx, y + dy
if not (nx >= 0 and nx < x_area_max and ny >= 0 and ny < y_area_max):
continue
if not allowed(maze[ny][nx], tool):
continue
r.append((nx, ny, tool))
return r
def distance(a, b):
if a == 0:
return 0
elif a[2] != b[2]:
assert a[0] == b[0] and a[1] == b[1]
return 7
else:
return 1
a = A_Star(
starts=[(0, 0, 0)],
is_goal=lambda s: s[0] == x_target and s[1] == y_target and s[2] == 0,
h=lambda s: abs(s[0] - x_target) + abs(s[1] - y_target),
d=distance,
neighbors=neighbors,
)
print(a.cost)

45
2018/d23.py Normal file
View File

@@ -0,0 +1,45 @@
from lib import get_data, ints
data = get_data(__file__)
X, Y, Z, RADIUS = 0, 1, 2, 3
def dist(a, b):
return sum(abs(x - y) for x, y in zip(a[:RADIUS], b[:RADIUS]))
bots = [ints(line) for line in data.splitlines()]
max_bot = max(bots, key=lambda b: b[RADIUS])
r = max_bot[3]
t = sum(1 for bot in bots if dist(bot, max_bot) <= r)
print(t)
from z3 import Int, If, Abs, Sum, IntVal, Optimize, set_param
x = Int("x")
y = Int("y")
z = Int("z")
z3zero = IntVal(0)
z3one = IntVal(1)
n_out_of_range = Int("n_out_of_range")
dist_origin = Int("dist_origin")
bots_out_of_range = [
If(Abs(b[X] - x) + Abs(b[Y] - y) + Abs(b[Z] - z) > b[RADIUS], z3one, z3zero)
for b in bots
]
set_param("parallel.enable", True)
opt = Optimize()
opt.add(n_out_of_range == Sum(bots_out_of_range))
opt.add(dist_origin == Abs(x) + Abs(y) + Abs(z))
opt.minimize(n_out_of_range)
opt.minimize(dist_origin)
res = opt.check()
m = opt.model()
print(m[dist_origin])

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

@@ -1,4 +1,5 @@
from lib import get_data, str_to_ints
from math import lcm
def freeze(coords, velos):
@@ -40,7 +41,40 @@ def part_1(data):
def part_2(data):
raise Exception("TBD")
steps = []
for i in range(3):
coords = []
velos = []
for line in data.splitlines():
coords.append(str_to_ints(line)[i])
velos.append(0)
seen = {}
for step in range(1_000_000):
state = tuple(coords) + tuple(velos)
if state in seen:
steps.append(step)
break
else:
seen[state] = step
for i in range(len(coords)):
for j in range(i + 1, len(coords)):
if coords[i] > coords[j]:
velos[i] -= 1
velos[j] += 1
elif coords[i] < coords[j]:
velos[i] += 1
velos[j] -= 1
# update coords
for i in range(len(coords)):
coords[i] += velos[i]
# Intuition: Find when position repeats on each axis and then find lowest
# common multiple for all three values.
print(lcm(*steps))
def main():

View File

@@ -40,7 +40,7 @@ def phase_with_offset(digits_in, pattern, offset):
digits_out = digits_in.copy()
for round in range(offset, len(digits_in)):
i, out = 0, 0
print(round)
# print(round)
pattern_value = pattern[((i + 1) // (round + 1)) % len(pattern)]
if pattern_value == 0:
@@ -53,8 +53,6 @@ def phase_with_offset(digits_in, pattern, offset):
out += pattern_value * sum(
digits_in[i : min(i + 1 + round, len(digits_in))]
)
# for j in range(i, min(i + 1 + round, len(digits_in))):
# out += (pattern_value * digits_in[j])
i += round + 1
out = abs(out) % 10
@@ -71,16 +69,33 @@ def part_1(data):
input = phase_with_offset(input, pattern, 0)
print("".join(map(str, input[:8])))
input = list(map(int, (data.strip()))) * 10_000
offset = int("".join(map(str, input[:7]))) - 200
out = list(map(int, (data.strip()))) * 10_000
offset = int("".join(map(str, out[:7])))
for _ in range(100):
for i in range(len(out) - 2, len(out) - 1_000_000, -1):
out[i] = abs(out[i] + out[i + 1]) % 10
print("".join(map(str, out[offset : offset + 8])))
for i in range(100):
print(i)
input = phase_with_offset(input, pattern, offset)
print(input)
return
# digits = 40
# for round in range(digits):
# s = ""
# for i in range(digits):
# pattern_i = ((i + 1) // (round + 1)) % len(pattern)
# pattern_value = pattern[pattern_i]
# if pattern_value == 0:
# s += " "
# elif pattern_value == 1:
# s += "+"
# elif pattern_value == -1:
# s += "-"
# else:
# assert False
# print(s)
# return
# Just here to document my thought process. Mental hack: Assume that you
# have the capability to solve the problem easily.
#
# What do I know?
#
# 1. There is a solution. Other people have solved it.

View File

@@ -51,9 +51,11 @@ def find_path(g):
path += "R"
dir = DIRS[(DIRS.index(dir) + 1) % len(DIRS)]
g[pos] = DIRCHAR[DIRS.index(dir)]
g.print()
input()
print()
# For debugging:
# g.print()
# input()
# print()
path = path.replace("RRR", "L")
return path
@@ -81,14 +83,48 @@ def part_1(data):
# g[(r, c)] = 'o'
result += r * c
print(result)
g.print()
path = find_path(g)
print(path)
# g.print()
# xs = str_to_ints(data)
# xs[0] = 2
# a = Amp(xs)
# a.go()
# Merge F commands into counts
path = find_path(g)
path = list(path)
new_path = []
i = 0
while i < len(path):
if path[i] == "R":
new_path.append("R")
i += 1
elif path[i] == "L":
new_path.append("L")
i += 1
elif path[i] == "F":
count = 0
while i < len(path) and path[i] == "F":
count += 1
i += 1
new_path.append(str(count))
else:
assert False
path = new_path
print("Manually translate into commands:", "".join(path))
# manually created from above output
inst = (
"A,A,B,C,B,C,B,C,A,C\n"
"R,6,L,8,R,8\n"
"R,4,R,6,R,6,R,4,R,4\n"
"L,8,R,6,L,10,L,10\n"
"n\n"
)
xs = str_to_ints(data)
xs[0] = 2
a = Amp(xs)
for c in inst:
a.feed(ord(c))
while not a.done:
a.go()
print(a.outputs[-1])
def main():

199
2019/d18.py Normal file
View File

@@ -0,0 +1,199 @@
from lib import get_data, Grid2D, LETTERS_UPPER, LETTERS_LOWER, add2
from collections import defaultdict
LETTERS = LETTERS_UPPER + LETTERS_LOWER
data = """#############
#g#f.D#..h#l#
#F###e#E###.#
#dCba...BcIJ#
#####.@.#####
#nK.L...G...#
#M###N#H###.#
#o#m..#i#jk.#
#############"""
def part_1(data):
g = Grid2D(data)
(start,) = g.find("@")
starts = [start]
graph = defaultdict(set)
starts_seen = set()
while starts:
start = starts.pop()
if start in starts_seen:
continue
else:
starts_seen.add(start)
start_symbol = g[start]
xs = [start]
seen = set()
for steps in range(1_000):
nxs = []
for x in xs:
if x in seen:
continue
else:
seen.add(x)
for nb in g.neighbors_ort(x):
if g[nb] == ".":
nxs.append(nb)
elif g[nb] in LETTERS:
symbol = g[nb]
if symbol != start_symbol:
graph[symbol].add((start_symbol, steps + 1))
graph[start_symbol].add((symbol, steps + 1))
starts.append(nb)
xs = nxs
if len(xs) == 0:
break
all_keys = [g[p] for p in g.find(LETTERS_LOWER)]
poss = [(0, 0, tuple("@"), "@")]
best: dict[tuple[tuple, str], int] = {(("@",), "@"): 0}
min_dist = 10**9
while poss:
current_distance, key_count, keys, symbol = poss.pop()
# print(current_distance, key_count, keys, symbol)
if key_count - 1 == len(all_keys):
min_dist = min(min_dist, current_distance)
continue
for next_symbol, distance in graph[symbol]:
if next_symbol in LETTERS_UPPER and not next_symbol.lower() in keys:
continue
if next_symbol in LETTERS_LOWER:
new_keys = set(keys)
new_keys.add(next_symbol)
new_keys = tuple(sorted(new_keys))
new_key_count = len(new_keys)
else:
new_keys = keys
new_key_count = key_count
new_distance = current_distance + distance
key = (new_keys, next_symbol)
if (key not in best) or (key in best and best[key] > new_distance):
best[key] = new_distance
poss.append((new_distance, new_key_count, new_keys, next_symbol))
print(min_dist)
def part_2(data):
g = Grid2D(data)
(start,) = g.find("@")
g[start] = "#"
g[add2(start, (-1, 0))] = "#"
g[add2(start, (1, 0))] = "#"
g[add2(start, (0, 1))] = "#"
g[add2(start, (0, -1))] = "#"
g[add2(start, (-1, -1))] = "0"
g[add2(start, (-1, 1))] = "1"
g[add2(start, (1, 1))] = "2"
g[add2(start, (1, -1))] = "3"
starts = g.find("0") + g.find("1") + g.find("2") + g.find("3")
graph = defaultdict(set)
starts_seen = set()
while starts:
start = starts.pop()
if start in starts_seen:
continue
else:
starts_seen.add(start)
start_symbol = g[start]
xs = [start]
seen = set()
for steps in range(1_000):
nxs = []
for x in xs:
if x in seen:
continue
else:
seen.add(x)
for nb in g.neighbors_ort(x):
if g[nb] == ".":
nxs.append(nb)
elif g[nb] in LETTERS:
symbol = g[nb]
if symbol != start_symbol:
graph[start].add((nb, steps + 1))
graph[nb].add((start, steps + 1))
starts.append(nb)
xs = nxs
if len(xs) == 0:
break
# g.print()
all_keys = [g[p] for p in g.find(LETTERS_LOWER)]
robots = tuple(g.find("0") + g.find("1") + g.find("2") + g.find("3"))
poss = [(0, tuple(), robots)]
best: dict[tuple[tuple, tuple], int] = {(tuple(), tuple()): 0}
min_dist = 10**9
while poss:
current_distance, keys, robots = poss.pop()
if len(keys) == len(all_keys):
min_dist = min(min_dist, current_distance)
# print(min_dist)
continue
# print(current_distance, keys, robots)
for robot_i in range(len(robots)):
robot = robots[robot_i]
# robot_symbol = g[robot]
for next_pos, distance in graph[robot]:
next_symbol = g[next_pos]
if next_symbol in LETTERS_UPPER and not next_symbol.lower() in keys:
continue
if next_symbol in LETTERS_LOWER:
new_keys = set(keys)
new_keys.add(next_symbol)
new_keys = tuple(sorted(new_keys))
else:
new_keys = keys
new_distance = current_distance + distance
new_robots = list(robots)
new_robots[robot_i] = next_pos
new_robots = tuple(new_robots)
key = (new_keys, new_robots)
if (key not in best) or (key in best and best[key] > new_distance):
best[key] = new_distance
poss.append((new_distance, new_keys, new_robots))
poss = sorted(poss, key=lambda xs: (xs[0], -len(xs[1])), reverse=True)
poss = poss[-10000:]
print(min_dist)
def main():
data = get_data(__file__)
part_1(data)
part_2(data)
if __name__ == "__main__":
main()

154
2019/d20.py Normal file
View File

@@ -0,0 +1,154 @@
from lib import get_data, Grid2D, LETTERS_UPPER
from collections import defaultdict
data = get_data(__file__)
g = Grid2D(data)
start = None
end = None
warps = defaultdict(list)
inner = set()
outer = set()
for row in range(g.n_rows - 2):
for col in range(g.n_cols - 2):
a, b, c = g[(row, col)], g[(row, col + 1)], g[(row, col + 2)]
x, y, z = g[(row, col)], g[(row + 1, col)], g[(row + 2, col)]
if a in LETTERS_UPPER and b in LETTERS_UPPER and c == ".":
warps[a + b].append((row, col + 2))
if col == 0:
outer.add((row, col + 2))
else:
inner.add((row, col + 2))
elif a == "." and b in LETTERS_UPPER and c in LETTERS_UPPER:
warps[b + c].append((row, col))
if col + 3 == g.n_cols:
outer.add((row, col))
else:
inner.add((row, col))
if x in LETTERS_UPPER and y in LETTERS_UPPER and z == ".":
if x + y == "AA":
start = (row + 2, col)
else:
warps[x + y].append((row + 2, col))
if row == 0:
outer.add((row + 2, col))
else:
inner.add((row + 2, col))
elif x == "." and y in LETTERS_UPPER and z in LETTERS_UPPER:
if y + z == "ZZ":
end = (row, col)
else:
warps[y + z].append((row, col))
if row + 3 == g.n_rows:
outer.add((row, col))
else:
inner.add((row, col))
graph = defaultdict(list)
allnodes = set([start, end])
for key, (a, b) in warps.items():
graph[a].append((b, 1))
graph[b].append((a, 1))
allnodes.add(a)
allnodes.add(b)
for startnode in allnodes:
to_visit = [startnode]
steps = 0
seen = set()
while to_visit:
steps += 1
new_to_visit = []
for node in to_visit:
if node in seen:
continue
else:
seen.add(node)
assert node is not None
for nb in g.neighbors_ort(node):
if nb in seen:
continue
if nb in allnodes:
if not (nb, steps) in graph[startnode]:
graph[startnode].append((nb, steps))
if not (startnode, steps) in graph[nb]:
graph[nb].append((startnode, steps))
seen.add(nb)
elif g[nb] == ".":
new_to_visit.append(nb)
to_visit = new_to_visit
shortest = {start: 0}
to_visit = [start]
seen = set()
while to_visit:
to_visit.sort(key=lambda node: shortest[node], reverse=True)
current = to_visit.pop()
if current in seen:
continue
else:
seen.add(current)
for nb, dist in graph[current]:
new_dist = shortest[current] + dist
if not nb in shortest:
shortest[nb] = new_dist
elif new_dist < shortest[nb]:
shortest[nb] = new_dist
if nb not in seen:
to_visit.append(nb)
print(shortest[end])
shortest = {(start, 0): 0}
to_visit = [(start, 0)]
seen = set()
while to_visit:
to_visit.sort(key=lambda node: shortest[node], reverse=True)
current, level = to_visit.pop()
if (current, level) in seen:
continue
else:
seen.add((current, level))
for nb, dist in graph[current]:
new_dist = shortest[(current, level)] + dist
if nb == end and level == 0:
print(new_dist)
to_visit = None
break
elif nb == end:
continue
elif nb == start:
continue
if dist == 1:
if current in inner:
new_level = level + 1
elif current in outer:
new_level = level - 1
else:
assert False
else:
new_level = level
if new_level < 0:
continue
nn = (nb, new_level)
if not nn in shortest:
shortest[nn] = new_dist
elif new_dist < shortest[nn]:
shortest[nn] = new_dist
to_visit.append(nn)

57
2019/d21.py Normal file
View File

@@ -0,0 +1,57 @@
from lib import get_data, str_to_ints
from d9 import Amp
data = get_data(__file__)
xs = str_to_ints(data)
script = """NOT A J
AND A J
NOT C T
AND D T
OR T J
NOT A T
OR T J
"""
a = Amp(xs)
while not a.done:
a.go()
if a.input_required:
for c in script:
a.feed(ord(c))
for c in "WALK\n":
a.feed(ord(c))
while a.outputs:
c = a.pop()
try:
chr(c)
# print(chr(c), end="")
except ValueError:
print(c)
script = """NOT C J
AND H J
NOT A T
OR T J
NOT B T
OR T J
AND D J
"""
a = Amp(xs)
while not a.done:
a.go()
if a.input_required:
for c in script:
a.feed(ord(c))
for c in "RUN\n":
a.feed(ord(c))
while a.outputs:
c = a.pop()
try:
chr(c)
# print(chr(c), end="")
except ValueError:
print(c)

118
2019/d22.py Normal file
View File

@@ -0,0 +1,118 @@
from lib import get_data, str_to_ints, mod_inverse
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)
deck = deck[n:] + deck[:n]
elif "increment" in line:
new_deck = [-1] * len(deck)
pos = 0
(n,) = str_to_ints(line)
deck = list(reversed(deck))
while deck:
new_deck[pos] = deck.pop()
pos = (pos + n) % len(new_deck)
deck = new_deck
else:
assert False
print(deck.index(2019))
len = 10007
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
assert index == deck.index(2019)
# 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)

57
2020/d10.py Normal file
View File

@@ -0,0 +1,57 @@
from lib import get_data
from collections import defaultdict
from functools import lru_cache
@lru_cache
def count(xs, current, target):
delta_target = target - current
if len(xs) == 0:
if delta_target <= 3:
return 1
else:
return 0
x = xs[0]
xs = xs[1:]
total = 0
delta = x - current
if delta > 3:
return 0
if delta <= 3:
total += count(xs, x, target)
total += count(xs, current, target)
return total
def part_1(data):
xs = list(map(int, data.strip().splitlines()))
out = max(xs) + 3
ds = defaultdict(int)
c = 0
for x in sorted(xs):
d = x - c
ds[d] += 1
c = x
d = out - c
ds[d] += 1
a, b = list(ds.values())
print(a * b)
xs = tuple(sorted(xs))
print(count(xs, 0, out))
def main():
data = get_data(__file__)
part_1(data)
if __name__ == "__main__":
main()

89
2020/d11.py Normal file
View File

@@ -0,0 +1,89 @@
from lib import get_data, Grid2D, add2
data = """L.LL.LL.LL
LLLLLLL.LL
L.L.L..L..
LLLL.LL.LL
L.LL.LL.LL
L.LLLLL.LL
..L.L.....
LLLLLLLLLL
L.LLLLLL.L
L.LLLLL.LL"""
def part_1(data):
g = Grid2D(data)
seen = set()
while True:
h = g.hash()
if h in seen:
break
seen.add(h)
ng = g.clone_with_val(".")
for r in range(g.n_rows):
for c in range(g.n_cols):
p = (r, c)
s = g[p]
if s == ".":
continue
occupied = sum([1 if g[nb] == "#" else 0 for nb in g.neighbors_adj(p)])
if s == "L" and occupied == 0:
ng[p] = "#"
elif s == "#" and occupied >= 4:
ng[p] = "L"
else:
ng[p] = s
g = ng
print(len(g.find("#")))
def part_2(data):
g = Grid2D(data)
seen = set()
while True:
h = g.hash()
if h in seen:
break
seen.add(h)
ng = g.clone_with_val(".")
for r in range(g.n_rows):
for c in range(g.n_cols):
p = (r, c)
s = g[p]
if s == ".":
continue
occupied = 0
for dir in g.COORDS_ORTH + g.COORDS_DIAG:
np = p
while True:
np = add2(np, dir)
if not g.contains(np):
break
elif g[np] == "#":
occupied += 1
break
elif g[np] == "L":
break
if s == "L" and occupied == 0:
ng[p] = "#"
elif s == "#" and occupied >= 5:
ng[p] = "L"
else:
ng[p] = s
g = ng
print(len(g.find("#")))
def main():
data = get_data(__file__)
part_1(data)
part_2(data)
if __name__ == "__main__":
main()

82
2020/d12.py Normal file
View File

@@ -0,0 +1,82 @@
from lib import get_data
DIRS = {
"N": (-1, 0),
"E": (0, 1),
"S": (1, 0),
"W": (0, -1),
}
def part_1(data):
dir_key = "E"
pos = 0, 0
dirs = "NESW"
for line in data.splitlines():
c = line[0]
v = int(line[1:])
if c in DIRS.keys():
pos = pos[0] + v * DIRS[c][0], pos[1] + v * DIRS[c][1]
elif c == "F":
dir = DIRS[dir_key]
pos = (pos[0] + v * dir[0], pos[1] + v * dir[1])
elif c == "R":
assert v % 90 == 0
dir_key = dirs[(dirs.index(dir_key) + v // 90) % len(dirs)]
elif c == "L":
assert v % 90 == 0
dir_key = dirs[(dirs.index(dir_key) - v // 90) % len(dirs)]
else:
print(c, v)
assert False
print(sum(map(abs, pos)))
def part_2(data):
way_point = (-1, 10)
pos = 0, 0
for line in data.splitlines():
c = line[0]
v = int(line[1:])
if c in DIRS.keys():
way_point = way_point[0] + v * DIRS[c][0], way_point[1] + v * DIRS[c][1]
elif c == "F":
pos = (pos[0] + v * way_point[0], pos[1] + v * way_point[1])
elif c == "R":
row, col = way_point
if v == 90:
way_point = (col, -row)
elif v == 180:
way_point = (-row, -col)
elif v == 270:
way_point = (-col, row)
else:
print(c, v)
assert False
elif c == "L":
row, col = way_point
if v == 90:
way_point = (-col, row)
elif v == 180:
way_point = (-row, -col)
elif v == 270:
way_point = (col, -row)
else:
print(c, v)
assert False
else:
print(c, v)
assert False
print(sum(map(abs, pos)))
def main():
data = get_data(__file__)
part_1(data)
part_2(data)
if __name__ == "__main__":
main()

39
2020/d13.py Normal file
View File

@@ -0,0 +1,39 @@
from lib import get_data, str_to_ints, mod_inverse
from functools import reduce
data = get_data(__file__)
lines = data.splitlines()
earliest = int(lines[0])
times = str_to_ints(lines[1])
mintime = 10**21
id = None
for time in times:
mintimecur = (earliest // time) * time + time
if mintimecur < mintime:
mintime = mintimecur
id = time
assert id is not None
print((mintime - earliest) * id)
fields = lines[1].split(",")
buses = []
for offset, busid in enumerate(fields):
if busid == "x":
continue
period = int(busid)
buses.append((period, -offset % period))
total = 0
product = reduce(lambda a, b: a * b, map(lambda x: x[0], buses))
for period, offset in buses:
p = product // period
total += offset * mod_inverse(p, period) * p
print(total % product)

77
2020/d14.py Normal file
View File

@@ -0,0 +1,77 @@
from lib import get_data, str_to_ints
from collections import defaultdict
data = """mask = XXXXXXXXXXXXXXXXXXXXXXXXXXXXX1XXXX0X
mem[8] = 11
mem[7] = 101
mem[8] = 0
"""
data = get_data(__file__)
mem = defaultdict(int)
mfix, mopt = None, None
for line in data.splitlines():
if line.startswith("mask"):
fix = "0b"
opt = "0b"
for c in line[7:]:
if c == "1":
fix += "1"
opt += "0"
elif c == "0":
fix += "0"
opt += "0"
elif c == "X":
fix += "0"
opt += "1"
else:
assert False
mfix = int(fix, 2)
mopt = int(opt, 2)
elif line.startswith("mem"):
assert mfix is not None and mopt is not None
addr, value = str_to_ints(line)
value = mfix | (value & mopt)
mem[addr] = value
print(sum(v for v in mem.values()))
mem = defaultdict(int)
masks = []
for line in data.splitlines():
if line.startswith("mask"):
fix = ["0b"]
opt = ["0b"]
for c in line[7:]:
if c == "0":
fix = [f + "0" for f in fix]
opt = [o + "1" for o in opt]
elif c == "1":
fix = [f + "1" for f in fix]
opt = [o + "0" for o in opt]
elif c == "X":
nfix = []
nopt = []
for f in fix:
nfix.append(f + "0")
nfix.append(f + "1")
for o in opt:
nopt.append(o + "0")
nopt.append(o + "0")
fix = nfix
opt = nopt
else:
assert False
masks = tuple(
zip(
tuple(map(lambda f: int(f, 2), fix)),
tuple(map(lambda o: int(o, 2), opt)),
)
)
elif line.startswith("mem"):
addr, value = str_to_ints(line)
for fix, opt in masks:
addr_ = fix | (addr & opt)
mem[addr_] = value
print(sum(v for v in mem.values()))

22
2020/d15.py Normal file
View File

@@ -0,0 +1,22 @@
from lib import get_data, str_to_ints
from collections import defaultdict
data = get_data(__file__)
for limit in [2021, 30000001]:
xs = list(reversed(str_to_ints(data)))
spoken = defaultdict(list)
recent = None
for turn in range(1, limit):
if len(xs) > 0:
recent = xs.pop()
spoken[recent].append(turn)
else:
if recent in spoken and len(spoken[recent]) == 1:
recent = 0
elif recent in spoken:
recent = spoken[recent][-1] - spoken[recent][-2]
else:
recent = 0
spoken[recent].append(turn)
print(recent)

88
2020/d16.py Normal file
View File

@@ -0,0 +1,88 @@
from lib import get_data, str_to_ints
data = get_data(__file__)
dps = []
ranges = []
lines = (l for l in data.splitlines())
index = 0
for line in lines:
if line.strip() == "":
break
if line.startswith("departure"):
dps.append(index)
a, b, c, d = str_to_ints(line.replace("-", " "))
# ranges.append(((a, b), (c, d)))
ranges.append((a, b, c, d))
index += 1
next(lines)
my_ticket = str_to_ints(next(lines))
next(lines)
next(lines)
error = 0
valid = []
fields = []
for line in lines:
# print(line)
has_error = False
field = []
for x in str_to_ints(line):
no_match = True
field.append(set())
for i, (a, b, c, d) in enumerate(ranges):
if a <= x <= b:
field[-1].add(i)
no_match = False
elif c <= x <= d:
field[-1].add(i)
no_match = False
if no_match:
has_error = True
error += x
if not has_error:
valid.append(line)
fields.append(field)
base = fields[0]
for field in fields[1:]:
for i in range(len(field)):
base[i] &= field[i]
print(error)
used = set()
done = False
base = [list(xs) for xs in base]
while not done:
done = True
single = None
for xs in base:
if len(xs) == 1 and xs[0] not in used:
single = xs[0]
used.add(single)
break
if single is not None:
for xs in base:
if len(xs) == 1:
continue
if single in xs:
xs.remove(single)
for xs in base:
if len(xs) != 1:
done = False
break
mapping = []
for xs in base:
(x,) = xs
mapping.append(x)
r = 1
for i, v in zip(mapping, my_ticket):
if i in dps:
r *= v
print(r)

42
2020/d17.py Normal file
View File

@@ -0,0 +1,42 @@
from itertools import product
from lib import get_data
from collections import defaultdict
data = get_data(__file__)
def neighbors(p):
d = len(p)
for xs in product(range(-1, 2), repeat=d):
if all(x == 0 for x in xs):
continue
yield tuple([p[i] + xs[i] for i in range(len(xs))])
for d in [3, 4]:
points = set()
lines = data.splitlines()
for y in range(len(lines)):
for x in range(len(lines[0])):
if lines[y][x] == "#":
points.add(tuple([x, y] + [0] * (d - 2)))
for _ in range(6):
new_points = set()
actives = defaultdict(int)
for p in points:
nbcount = 0
for nb in neighbors(p):
actives[nb] += 1
if nb in points:
nbcount += 1
if nbcount == 2 or nbcount == 3:
new_points.add(p)
for p, count in actives.items():
if p in points:
continue
elif count == 3:
new_points.add(p)
points = new_points
print(len(points))

106
2020/d18.py Normal file
View File

@@ -0,0 +1,106 @@
from lib import get_data
def eval_1(s) -> int:
if type(s) is int:
return s
i = 0
parts = []
while i < len(s):
if s[i] == "(":
nested = 1
r = "("
i += 1
while nested > 0:
r += s[i]
if s[i] == ")":
nested -= 1
elif s[i] == "(":
nested += 1
i += 1
parts.append(r[1:-1])
elif s[i].isdigit():
d = ""
while i < len(s) and s[i].isdigit():
d += s[i]
i += 1
parts.append(int(d))
elif s[i] == " ":
pass
elif s[i] == "*" or s[i] == "+":
parts.append(s[i])
i += 1
i = 0
while len(parts) > 1:
if parts[1] == "*":
parts[2] = eval_1(parts[0]) * eval_1(parts[2])
parts = parts[2:]
elif parts[1] == "+":
parts[2] = eval_1(parts[0]) + eval_1(parts[2])
parts = parts[2:]
else:
assert False
return parts[-1]
def eval_2(s) -> int:
if type(s) is int:
return s
i = 0
parts = []
while i < len(s):
if s[i] == "(":
nested = 1
r = "("
i += 1
while nested > 0:
r += s[i]
if s[i] == ")":
nested -= 1
elif s[i] == "(":
nested += 1
i += 1
parts.append(r[1:-1])
elif s[i].isdigit():
d = ""
while i < len(s) and s[i].isdigit():
d += s[i]
i += 1
parts.append(int(d))
elif s[i] == " ":
pass
elif s[i] == "*" or s[i] == "+":
parts.append(s[i])
i += 1
while len(parts) > 1:
new_parts = []
for i in range(0, len(parts) - 1, 2):
if parts[i + 1] == "+":
new_parts.append(eval_2(parts[i]) + eval_2(parts[i + 2]))
new_parts += parts[i + 3 :]
break
elif parts[1] == "*":
new_parts.append(parts[i])
new_parts.append(parts[i + 1])
else:
assert False
else:
assert parts[1] == "*"
new_parts = [eval_2(parts[0]) * eval_2(parts[2])]
new_parts += parts[3:]
parts = new_parts
return parts[-1]
data = get_data(__file__)
t1, t2 = 0, 0
for line in data.splitlines():
t1 += eval_1(line)
t2 += eval_2(line)
print(t1)
print(t2)
assert t2 == 290726428573651

62
2020/d19.py Normal file
View File

@@ -0,0 +1,62 @@
from lib import get_data
data = get_data(__file__)
rules = {}
msgs = []
for line in data.splitlines():
if len(line.strip()) == 0:
continue
if ":" in line:
id, rule = line.split(":")
id = int(id)
branches = [[]]
for part in rule.split():
if part.startswith('"') and part.endswith('"'):
branches = part[1]
elif part == "|":
assert type(branches) is list
branches.append([])
else:
assert type(branches) is list
branches[-1].append(int(part))
rules[id] = branches
else:
msgs.append(line.strip())
def matches(xs, rule):
if xs == "" and rule == []:
return True
elif xs == "" or rule == []:
return False
current = rules[rule[0]]
if current == xs[0]:
return matches(xs[1:], rule[1:])
elif type(current) is str:
return False
elif len(current) >= 1:
for ys in current:
if matches(xs, ys + rule[1:]):
return True
return False
t = 0
for m in msgs:
if matches(m, rules[0][0]):
t += 1
print(t)
rules[8] = [[42], [42, 8]]
rules[11] = [[42, 31], [42, 11, 31]]
t = 0
for m in msgs:
if matches(m, rules[0][0]):
t += 1
print(t)

169
2020/d20.py Normal file
View File

@@ -0,0 +1,169 @@
from collections import defaultdict
from lib import get_data, ints
from math import isqrt
data = get_data(__file__)
def top(rows):
return tuple(rows[0])
def bottom(rows):
return tuple(rows[-1])
def left(rows):
return tuple(tuple(row[0] for row in rows))
def right(rows):
return tuple(tuple(row[-1] for row in rows))
def fliph(rows):
return tuple(tuple(row[::-1] for row in rows))
def flipv(rows):
return tuple(rows[::-1])
def rot90(rows):
return tuple(tuple(row) for row in zip(*rows[::-1]))
def rot180(rows):
return rot90(rot90(rows))
def rot270(rows):
return rot90(rot90(rot90(rows)))
TOP = 0
RIGHT = 1
BOTTOM = 2
LEFT = 3
def all(rows) -> list:
return [
rows,
rot90(rows),
rot180(rows),
rot270(rows),
fliph(rows),
flipv(rows),
rot90(fliph(rows)),
rot90(flipv(rows)),
# rot180(fliph(rows)),
# rot180(flipv(rows)),
# rot270(fliph(rows)),
# rot270(flipv(rows)),
]
tiles = []
for p in data.strip().split("\n\n"):
lines = p.splitlines()
(id,) = ints(lines[0])
rowst = tuple(map(tuple, lines[1:]))
tiles.append((id, all(rowst)))
rights = defaultdict(list)
bottoms = defaultdict(list)
for id, variants in tiles:
for variant in variants:
rights[left(variant)].append((id, variant))
bottoms[top(variant)].append((id, variant))
num_tiles = len(tiles)
rows = isqrt(len(tiles))
def dfs(id_set, id_list, tiles_used):
if len(tiles_used) == num_tiles:
return id_list, tiles_used
i = len(tiles_used)
id_variants = None
if i < rows: # first row
id_variants = rights[right(tiles_used[i - 1])]
elif i % rows == 0: # first tile in row
id_variants = bottoms[bottom(tiles_used[i - rows])]
else:
id_variants = set(bottoms[bottom(tiles_used[i - rows])])
id_variants &= set(rights[right(tiles_used[i - 1])])
id_variants = list(id_variants)
assert id_variants is not None
for id, variant in id_variants:
if id in id_set:
continue
id_set.add(id)
id_list.append(id)
tiles_used.append(variant)
r = dfs(id_set, id_list, tiles_used)
if r is not False:
return r
tiles_used.pop()
id_set.remove(id)
id_list.pop()
return False, None
tiles_used = None
for id, tile_variants in tiles:
for tile in tile_variants:
r, tiles_used = dfs(set([id]), [id], [tile])
if type(r) is list:
print(r[0] * r[-1] * r[rows - 1] * r[-rows])
break
if tiles_used is not None:
break
else:
print("no result")
exit(0)
tiles_used = [[tile[x][1:-1] for x in range(1, len(tile) - 1)] for tile in tiles_used]
merged_tiles = []
for i in range(0, rows * rows, rows):
for row in range(len(tiles_used[0])):
merged_row = []
for j in range(rows):
merged_row.extend(tiles_used[i + j][row])
merged_tiles.append(merged_row)
seemonster = """ #
# ## ## ###
# # # # # # """
offsets: list[tuple[int, int]] = []
for row, line in enumerate(seemonster.splitlines()):
for col, c in enumerate(line):
if c == "#":
offsets.append((row, col))
total_hashes = sum([row.count("#") for row in merged_tiles])
seemonster_hashes = len(offsets)
max_seemonster_count = 0
for field in all(merged_tiles):
seemonster_count = 0
for ri in range(len(field) - 2):
for ci in range(len(field[0]) - 19):
for ro, co in offsets:
if field[ri + ro][ci + co] != "#":
break
else:
seemonster_count += 1
max_seemonster_count = max(max_seemonster_count, seemonster_count)
print(total_hashes - max_seemonster_count * seemonster_hashes)

50
2020/d21.py Normal file
View File

@@ -0,0 +1,50 @@
from collections import defaultdict
from lib import get_data
data = get_data(__file__)
algs_to_ings = defaultdict(set)
ings_counts = defaultdict(int)
for line in data.splitlines():
a, b = line.split(" (contains ")
ings = a.split(" ")
algs = b[:-1].split(", ")
for ing in ings:
ings_counts[ing] += 1
for a in algs:
if a not in algs_to_ings:
algs_to_ings[a] = set(ings)
else:
algs_to_ings[a] &= set(ings)
for ings in algs_to_ings.values():
for ing in ings:
if ing in ings_counts:
del ings_counts[ing]
t = sum(ings_counts.values())
print(t)
algs_to_ings = {a: list(ing) for a, ing in algs_to_ings.items()}
handled = set()
while True:
to_remove = None
for alg, ings in algs_to_ings.items():
if len(ings) == 1 and ings[0] not in handled:
to_remove = ings[0]
break
else:
break
assert to_remove is not None
handled.add(to_remove)
for ings in algs_to_ings.values():
if len(ings) > 1 and to_remove in ings:
ings.remove(to_remove)
xs = sorted([(a, i[0]) for a, i in algs_to_ings.items()])
xs = ",".join([x[1] for x in xs])
print(xs)

86
2020/d22.py Normal file
View File

@@ -0,0 +1,86 @@
from collections import deque, defaultdict
from lib import get_data, ints
data = get_data(__file__)
a, b = data.strip().split("\n\n")
a = deque(ints(a)[1:])
b = deque(ints(b)[1:])
while a and b:
x, y = a.popleft(), b.popleft()
if x > y:
a.append(x)
a.append(y)
elif y > x:
b.append(y)
b.append(x)
else:
assert False
for p in [a, b]:
s = 0
for i, c in enumerate(reversed(p)):
s += (i + 1) * c
if s > 0:
print(s)
CACHE = {}
def play(a, b):
if len(a) == 0 or len(b) == 0:
return a, b
s = (tuple(a), tuple(b))
if s in CACHE:
return CACHE[s]
seen = set()
while a and b:
s = (tuple(a), tuple(b))
if s in seen:
return True, False
seen.add(s)
x, y = a.popleft(), b.popleft()
if x <= len(a) and y <= len(b):
ar, br = play(deque(list(a)[:x]), deque(list(b)[:y]))
if ar:
a.append(x)
a.append(y)
elif br:
b.append(y)
b.append(x)
else:
assert False
else:
if x > y:
a.append(x)
a.append(y)
elif y > x:
b.append(y)
b.append(x)
else:
assert False
s = (tuple(a), tuple(b))
if s not in CACHE:
CACHE[s] = (a, b)
return a, b
a, b = data.strip().split("\n\n")
a = deque(ints(a)[1:])
b = deque(ints(b)[1:])
a, b = play(a, b)
for p in [a, b]:
s = 0
for i, c in enumerate(reversed(p)):
s += (i + 1) * c
if s > 0:
print(s)

73
2020/d23.py Normal file
View File

@@ -0,0 +1,73 @@
from lib import get_data
data = get_data(__file__)
cups = list(map(int, list(data.strip())))
min_label = min(cups)
max_label = max(cups)
cmap = {}
for i in range(len(cups)):
cmap[cups[i]] = cups[(i + 1) % len(cups)]
cc = cups[0]
for _ in range(100):
a = cmap[cc]
b = cmap[a]
c = cmap[b]
cmap[cc] = cmap[c]
dest = cc - 1
while dest in {a, b, c} or dest < min_label:
dest = dest - 1
if dest < min_label:
dest = max_label
cmap[c] = cmap[dest]
cmap[dest] = a
cc = cmap[cc]
s = ""
cup = cmap[1]
while cup != 1:
s += str(cup)
cup = cmap[cup]
print(s)
cups = list(map(int, list(data.strip())))
current = max(cups) + 1
while len(cups) != 1_000_000:
cups.append(current)
current += 1
min_label = min(cups)
max_label = max(cups)
cmap = {}
for i in range(len(cups)):
cmap[cups[i]] = cups[(i + 1) % len(cups)]
cc = cups[0]
for _ in range(10_000_000):
a = cmap[cc]
b = cmap[a]
c = cmap[b]
cmap[cc] = cmap[c]
dest = cc - 1
while dest in {a, b, c} or dest < min_label:
dest = dest - 1
if dest < min_label:
dest = max_label
cmap[c] = cmap[dest]
cmap[dest] = a
cc = cmap[cc]
a = cmap[1]
b = cmap[a]
print(a * b)

59
2020/d24.py Normal file
View File

@@ -0,0 +1,59 @@
from lib import get_data, add2
from collections import defaultdict
DIRS = {
"e": (0, 2),
"w": (0, -2),
"se": (1, 1),
"sw": (1, -1),
"nw": (-1, -1),
"ne": (-1, 1),
}
data = get_data(__file__)
tiles_black = set()
for row in data.splitlines():
row = row.strip()
i = 0
pos = (0, 0)
while i < len(row):
d = None
if row[i] in DIRS:
d = DIRS[row[i]]
i += 1
elif "".join(row[i : i + 2]) in DIRS:
d = DIRS["".join(row[i : i + 2])]
i += 2
else:
assert False
assert d is not None
pos = add2(pos, d)
if pos in tiles_black:
tiles_black.remove(pos)
else:
tiles_black.add(pos)
print(len(tiles_black))
for _ in range(100):
nbs = defaultdict(int)
for bt in tiles_black:
for d in DIRS.values():
nb = add2(bt, d)
nbs[nb] += 1
new_tiles_black = set()
for pos, count in nbs.items():
if pos in tiles_black:
if count == 0 or count > 2:
pass
else:
new_tiles_black.add(pos)
else:
if count == 2:
new_tiles_black.add(pos)
tiles_black = new_tiles_black
print(len(tiles_black))

32
2020/d25.py Normal file
View File

@@ -0,0 +1,32 @@
from lib import get_data, ints
data = get_data(__file__)
a, b = ints(data)
v = 1
subject_number = 7
al, bl = None, None
for loop in range(100_000_000):
v *= subject_number
v %= 20201227
if al is None and v == a:
al = loop + 1
if bl is None and v == b:
bl = loop + 1
if al and bl:
break
assert al is not None
assert bl is not None
# print(al, bl)
v = 1
subject_number = b
for _ in range(al):
v *= subject_number
v %= 20201227
print(v)

View File

@@ -1,4 +1,4 @@
from lib import get_data, Grid2D
from lib import get_data
required = [
"byr",
@@ -7,7 +7,8 @@ required = [
"hgt",
"hcl",
"ecl",
"pid",]
"pid",
]
# hgt (Height) - a number followed by either cm or in:

40
2020/d5.py Normal file
View File

@@ -0,0 +1,40 @@
from lib import get_data
def part_1(data):
rs = []
for line in data.splitlines():
rl, ru = 0, 127
cl, cu = 0, 7
for c in line.strip():
rh = (ru - rl) // 2
ch = (cu - cl) // 2
if c == "B":
rl = rl + rh + 1
elif c == "F":
ru = rl + rh
elif c == "R":
cl = cl + ch + 1
elif c == "L":
cu = cl + ch
else:
assert False
assert rl == ru
assert cl == cu
r_new = rl * 8 + cl
rs.append(r_new)
print(max(rs))
rs = sorted(rs)
for i in range(len(rs) - 1):
if rs[i] + 1 != rs[i + 1]:
print(rs[i] + 1)
def main():
data = get_data(__file__)
part_1(data)
if __name__ == "__main__":
main()

24
2020/d6.py Normal file
View File

@@ -0,0 +1,24 @@
from lib import get_data
def part_1(data):
r1, r2 = 0, 0
for group in data.split("\n\n"):
r1 += len(set(group.replace("\n", "")))
s = set(group.splitlines()[0])
for line in group.splitlines():
s &= set(line)
r2 += len(s)
print(r1)
print(r2)
def main():
data = get_data(__file__)
part_1(data)
if __name__ == "__main__":
main()

47
2020/d7.py Normal file
View File

@@ -0,0 +1,47 @@
from lib import get_data
def part_1(data):
bags = {}
for line in data.splitlines():
left, right = line.split(" contain ")
lb = " ".join(left.split()[:2])
rbs = right.replace(" bag", "").replace(" bags", "").replace(".", "").split(",")
if "no others" in rbs:
rbs = []
else:
rbs = [[int(v.split()[0]), " ".join(v.split()[1:])] for v in rbs]
assert lb not in bags
bags[lb] = rbs
for lb, rbs in bags.items():
for t in rbs:
if t[0] > 1:
t[1] = t[1][:-1]
can_hold = set(["shiny gold"])
can_hold_count = -1
while can_hold_count != len(can_hold):
can_hold_count = len(can_hold)
for lb, rbs in bags.items():
for _, bag_color in rbs:
if bag_color in can_hold:
can_hold.add(lb)
print(len(can_hold) - 1)
def count(color):
r = 1 # the bag itself
for c, icolor in bags[color]:
r += c * count(icolor)
return r
print(count("shiny gold") - 1)
def main():
data = get_data(__file__)
part_1(data)
if __name__ == "__main__":
main()

71
2020/d8.py Normal file
View File

@@ -0,0 +1,71 @@
from lib import get_data, str_to_ints
def part_1(data):
insts = data.splitlines()
acc = 0
i = 0
seen = set()
while i < len(insts) and i not in seen:
inst = insts[i]
seen.add(i)
if inst.startswith("acc"):
(v,) = str_to_ints(inst)
acc += v
i += 1
elif inst.startswith("jmp"):
(v,) = str_to_ints(inst)
i += v
elif inst.startswith("nop"):
i += 1
else:
assert False
print(acc)
for j in range(len(insts)):
if "jmp" in insts[j]:
insts[j] = insts[j].replace("jmp", "nop")
elif "nop" in insts[j]:
insts[j] = insts[j].replace("nop", "jmp")
else:
continue
i = 0
acc = 0
seen = set()
while i < len(insts) and i not in seen:
inst = insts[i]
seen.add(i)
if inst.startswith("acc"):
(v,) = str_to_ints(inst)
acc += v
i += 1
elif inst.startswith("jmp"):
(v,) = str_to_ints(inst)
i += v
elif inst.startswith("nop"):
i += 1
else:
assert False
if not i in seen:
print(acc)
if "jmp" in insts[j]:
insts[j] = insts[j].replace("jmp", "nop")
elif "nop" in insts[j]:
insts[j] = insts[j].replace("nop", "jmp")
else:
continue
def main():
data = get_data(__file__)
part_1(data)
if __name__ == "__main__":
main()

33
2020/d9.py Normal file
View File

@@ -0,0 +1,33 @@
from lib import get_data
def part_1(data):
target = 0
xs = list(map(int, data.splitlines()))
for i in range(25, len(xs)):
x = xs[i]
good = False
for j in range(i - 25, i):
for k in range(j, i):
if xs[j] + xs[k] == x:
good = True
if not good:
print(x)
target = x
break
for i in range(len(xs)):
for j in range(i + 1, len(xs)):
if sum(xs[i : j + 1]) == target:
r = xs[i : j + 1]
print(min(r) + max(r))
return
def main():
data = get_data(__file__)
part_1(data)
if __name__ == "__main__":
main()

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

View File

@@ -1,117 +1,66 @@
from lib import *
import re
from lib import get_data, str_to_ints
from math import ceil
EXAMPLE = """
Blueprint 1:
Each ore robot costs 4 ore.
Each clay robot costs 2 ore.
Each obsidian robot costs 3 ore and 14 clay.
Each geode robot costs 2 ore and 7 obsidian.
data = get_data(__file__)
Blueprint 2:
Each ore robot costs 2 ore.
Each clay robot costs 3 ore.
Each obsidian robot costs 3 ore and 8 clay.
Each geode robot costs 3 ore and 12 obsidian.
"""
p1 = 0
p2 = 1
def solve(input: Input, second=False):
if not second:
res = 0
else:
res = 1
for line in data.splitlines():
id = str_to_ints(line)[0]
bps = []
for line in input.text.replace(".", ".\n").replace(":", ":\n").splitlines():
if "Blueprint" in line:
bps.append([])
elif "ore robot" in line:
cost = str_to_ints(line)
cost += [0, 0]
bps[-1].append(cost)
elif "clay robot" in line:
cost = str_to_ints(line)
cost += [0, 0]
bps[-1].append(cost)
elif "obsidian robot" in line:
cost = str_to_ints(line)
cost += [0,]
bps[-1].append(cost)
elif "geode robot" in line:
cost = str_to_ints(line)
cost.insert(1, 0)
bps[-1].append(cost)
maxmin = [0, 0, 0]
for part in line.split(". "):
bp = []
for amount, mineral in re.findall(r"(\d+) (\w+)", part):
amount = int(amount)
mi = ["ore", "clay", "obsidian", "geode"].index(mineral)
bp.append((amount, mi))
maxmin[mi] = max(maxmin[mi], amount)
bps.append(bp)
if second:
bps = bps[:3]
time = 32
else:
time = 24
def dfs(time, mins, bots, cache):
if time == 0:
return mins[3]
end_states = []
for i, bp in enumerate(bps):
# ((ore bots, clay bots, obs bots, geo bots), (ore, clay, obs, geo))
start = ((1, 0, 0, 0), (0, 0, 0, 0))
states = [start]
seen = set(states)
key = (time, *mins, *bots)
if key in cache:
return cache[key]
for _ in range(time):
new_states = []
while states:
bots, ress = states.pop()
maxv = mins[3] + time * bots[3]
for bi, recipe in enumerate(bps):
if bi != 3 and bots[bi] >= maxmin[bi]:
continue
add_ress = [0, 0, 0, 0]
for boti, count in enumerate(bots):
add_ress[boti] += count
wait = 0
for ra, ri in recipe:
if bots[ri] == 0:
break
wait = max(ceil((ra - mins[ri]) / bots[ri]), wait)
else:
remtime = time - wait - 1
if remtime <= 0:
continue
all_built = True
for boti, cost in enumerate(bp):
if ress[0] >= cost[0] and ress[1] >= cost[1] and ress[2] >= cost[2]:
new_ress = list(ress)
new_ress[0] -= cost[0]
new_ress[1] -= cost[1]
new_ress[2] -= cost[2]
new_ress = tuple(map(sum, zip(new_ress, add_ress)))
new_bots = list(bots)
new_bots[boti] += 1
new_state = (tuple(new_bots), new_ress)
if not new_state in seen:
new_states.append(new_state)
seen.add(new_state)
else:
all_built = False
bots_ = list(bots)
mins_ = [m + b * (wait + 1) for m, b in zip(mins, bots)]
for ra, ri in recipe:
mins_[ri] -= ra
bots_[bi] += 1
# XXX: our search space is too large here it is possible to
# optimze by not storing reduntant paths (paths where we acrue
# more of a resource than we need), but I don't know how to
# make it more efficient right now.
if not all_built:
new_ress = tuple(map(sum, zip(ress, add_ress)))
new_state = (bots, new_ress)
if not new_state in seen:
new_states.append(new_state)
seen.add(new_state)
for i in range(3):
mins_[i] = min(mins_[i], maxmin[i] * remtime)
v = dfs(remtime, tuple(mins_), tuple(bots_), cache)
maxv = max(v, maxv)
# prune to keep search space "reasonable"
states = sorted(new_states, key=lambda s: list(reversed(s[0])), reverse=True)[:100000]
key = (time, *mins, *bots)
cache[key] = maxv
return maxv
if not second:
r = max(states, key=lambda s: s[1][3])
q = (i + 1) * r[1][3]
# print(i + 1, r, q)
res += q
else:
r = max(states, key=lambda r: r[1][3])
res *= r[1][3]
return res
p1 += id * dfs(24, (0, 0, 0, 0), (1, 0, 0, 0), {})
if id < 4:
p2 *= dfs(32, (0, 0, 0, 0), (1, 0, 0, 0), {})
def main():
DAY_INPUT = "d19.txt"
e1 = solve(Input(EXAMPLE))
print("Example 1:", e1)
assert e1 == 33
print("Solution 1:", solve(Input(DAY_INPUT)))
print("Example 2:", solve(Input(EXAMPLE), True))
print("Solution 2:", solve(Input(DAY_INPUT), True))
return
if __name__ == "__main__":
main()
print(p1)
print(p2)

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

Some files were not shown because too many files have changed in this diff Show More