Compare commits

...

131 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
631205086d Solve 2020 day 4 2024-08-26 21:23:57 -04:00
04d8c44d66 Started with 2020 because I need stars for Beeminder 2024-08-19 09:42:10 -04:00
6b0d7057d1 Add time 2024-08-18 20:27:03 -04:00
dd901c9f88 Solve easy d19 2019 because I need stars 2024-08-18 20:24:29 -04:00
e9f5542be8 2019 day 16 and 17 wip 2024-08-18 19:38:13 -04:00
ebf65905b4 2019 day 16 wip 2024-08-18 10:35:16 -04:00
aba7ad5bde 2019 day 16 in-progress 2024-08-17 16:01:06 -04:00
f88eb14d24 Solve 2019 day 14 and 15 2024-08-16 19:45:10 -04:00
6efa70ea51 Solve 2019 day 13 2024-08-15 16:21:11 -04:00
fb1e2183cc Solve 2019 day 11 2024-08-10 16:49:20 -04:00
e46636b9b0 Solve 2019 day 10 2024-08-10 14:22:19 -04:00
f116293066 Solve 2019 day 9 2024-08-09 19:30:21 -04:00
ef8a247ad6 Minor cleanup 2019 d8 2024-08-08 22:20:58 -04:00
af4507f785 Solve 2019 day 8 2024-08-08 21:46:57 -04:00
6f71ed1717 2019 day 6 and 7 2024-08-03 16:31:06 -04:00
9718165d7b 2019 day 4 and 5 2024-08-02 21:16:46 -04:00
b0e2334427 2019 day 2 and 3 2024-07-30 08:32:57 -04:00
29a556fd54 2019 day 1 to not derail 2024-07-28 19:13:13 -04:00
d2417a9377 Day 19 2018 wip 2024-07-28 19:04:25 -04:00
9f616a5b81 Solve 2018 day 18 2024-07-21 16:16:14 -04:00
b88326a7d1 Solve 2018 day 17 2024-07-14 15:28:50 -04:00
ab2e6f4ddb Solve 2018 day 16 and add function to download input 2024-07-14 10:05:18 -04:00
db82589857 Solve 2018 day 15 2024-07-13 15:23:09 -04:00
9346e3f4d0 Solve 2018 day 14 2024-07-13 12:09:54 -04:00
5e68e08807 Solve 2018 day 13 2024-07-13 11:24:58 -04:00
f04f8ed426 Solve 2018 day 12 2024-07-13 10:08:16 -04:00
26f523047e Solve day 9 till 11 and part 1 day 12 2018 2024-07-09 17:45:08 -04:00
e9ba9cee63 Add 2022 solutions 2024-07-07 20:34:52 -04:00
d582dfc777 Add 2023 solutions 2024-07-07 20:30:53 -04:00
da1c37ffa7 Solve day 7 and 8 of 2018. 2024-07-07 20:25:04 -04:00
9df26ebe71 Solve 2018 day 5 and 6. Nice. 2024-07-07 19:20:20 -04:00
3a915cb9e3 Start solving 2018 problems
I have also updated get.py to download the problems as
`d<day>.txt` instead of `i<day>.txt`. That allows me
to get the day input via `__input__.replace('.py', '.txt')`
which is a little more concise. I don't know why
I didn't do this earlier.
2024-07-04 11:10:27 -04:00
dcfae2cb13 Finish 2017. 2024-06-09 11:52:25 -04:00
f62f5fcff2 Solve day 24 2017. 2024-06-09 04:58:42 -04:00
869a22a310 Solve day 21 and 22 2017. 2024-06-03 12:06:02 -04:00
8042b7f5e2 Solve day 20. 2024-05-30 15:37:22 -04:00
ecda947fb4 Solve day 18 and 19 2017. 2024-05-30 09:01:51 -04:00
5b7b9c87cb Solve 2017 day 14 to 17. 2024-05-29 02:35:48 -04:00
4ba0b25b77 Solve day 13 2017. 2024-05-21 22:00:35 -04:00
1a71ab97c3 Solve a couple of 2017 problems. 2024-05-21 20:53:55 -04:00
6f63a866e1 Finish 2016. 2024-05-20 20:00:34 -04:00
90b2134736 Solve 2016 day 21. 2024-05-18 21:36:55 -04:00
a49eb9e8cb Solve 2016 day 20. 2024-05-18 10:12:27 -04:00
97e9fac07b Solve 2017 day 19. 2024-05-18 08:48:54 -04:00
78b1981ddf 2017 progress. 2024-05-18 07:38:03 -04:00
3c290b8da3 Solve 2016 day 18. 2024-05-08 18:58:15 -04:00
4e6a44f67b Finish 2015. 2024-05-08 18:29:48 -04:00
253 changed files with 17372 additions and 513 deletions

2
.gitignore vendored
View File

@@ -4,6 +4,8 @@
__pycache__/ __pycache__/
*.py[cod] *.py[cod]
*$py.class *$py.class
.python-version
uv.lock
# C extensions # C extensions
*.so *.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 floor = 0
for i, c in enumerate(data): for i, c in enumerate(data):
@@ -10,13 +11,6 @@ for i, c in enumerate(data):
floor += 1 floor += 1
elif c == ")": elif c == ")":
floor -= 1 floor -= 1
else: if floor == -1:
print(c)
assert False
if part_2 and floor == -1:
print(i + 1) print(i + 1)
break break
if not part_2:
print(floor)

View File

@@ -1,35 +1,29 @@
from functools import lru_cache from lib import get_data
data = open(0).read().strip()
part_1 = False data = get_data(__file__)
if part_1:
repeats = 40
else:
repeats = 50
@lru_cache
def look_and_say(data: str) -> str: def iter(xs):
r = "" if not xs:
i = 0 return []
while i < len(data): grouped = []
count = 0 current_element = xs[0]
c = data[i] count = 1
while i < len(data) and data[i] == c: for element in xs[1:]:
if element == current_element:
count += 1 count += 1
i += 1 else:
r += f"{count}{c}" grouped.append(count)
return r 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): def is_valid(pw):
# Passwords must include one increasing straight of at least three letters, # Passwords must include one increasing straight of at least three letters,
@@ -26,24 +29,26 @@ def is_valid(pw):
return False return False
return True return True
def inc(pw): def inc(pw):
pw = list(map(ord, pw)) pw = list(map(ord, pw))
for i in range(len(pw) - 1, -1, -1): for i in range(len(pw) - 1, -1, -1):
pw[i] += 1 pw[i] += 1
if pw[i] == ord('z') + 1: if pw[i] == ord("z") + 1:
pw[i] = ord('a') pw[i] = ord("a")
else: else:
break break
return "".join(map(chr, pw)) return "".join(map(chr, pw))
part_1 = False
part_1 = True
valid_count = 0 valid_count = 0
while True: while True:
data = inc(data) data = inc(data)
if is_valid(data): if is_valid(data):
valid_count += 1 valid_count += 1
if part_1 and valid_count == 1: if part_1 and valid_count == 1:
break print(data)
elif valid_count == 2: elif valid_count == 2:
break print(data)
print(data) break

View File

@@ -1,27 +1,37 @@
from lib import get_data
import json import json
data = open(0).read().strip() data = get_data(__file__).strip()
part_2 = False
def addup(d): o = json.loads(data)
r = 0
if isinstance(d, list):
for e in d: def xsum(obj, part_2=False):
v = addup(e) t = 0
if v is not None: if type(obj) is int:
r += v t += obj
elif isinstance(d, dict): elif type(obj) is list:
for e in d.values(): for o in obj:
v = addup(e) t += xsum(o, part_2)
if v is None: elif type(obj) is dict:
return 0 if part_2:
r += v st = 0
elif isinstance(d, str): for o in obj.values():
if part_2 and d == "red": st += xsum(o, part_2)
return None 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: else:
r += d print(obj)
return r 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 from itertools import permutations
data = open(0).read().strip()
part_2 = False data = get_data(__file__).strip()
pairs = defaultdict(int)
people = set() people = set()
scores = {}
for line in data.splitlines(): for line in data.splitlines():
a, _, wl, score, _, _, _, _, _, _, b = line.split() a, _, gainlose, amount, _, _, _, _, _, _, b = line[:-1].split()
b = b.replace(".", "") amount = int(amount)
score = int(score) if gainlose == "lose":
if wl == "lose": amount = -amount
score = -score pairs[(a, b)] = amount
people.add(a) 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 def calc_gain(people):
for p in permutations(list(people)): maxgain = 0
s = 0 for xs in permutations(list(people)):
for i in range(len(p)): gain = 0
a, b = p[i], p[(i + 1) % len(p)] for i in range(len(xs)):
s += scores[(a, b)] gain += pairs[(xs[i], xs[(i + 1) % len(xs)])]
s += scores[(b, a)] gain += pairs[(xs[(i + 1) % len(xs)], xs[i])]
max_score = max(max_score, s) 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 from lib import get_data
data = open(0).read().strip() from lib import ints, fst
from collections import defaultdict
part_1 = False data = get_data(__file__).strip()
deers = []
rds = []
max_dist = 0
gtime = 2503
rds = []
for line in data.splitlines(): for line in data.splitlines():
speed, time, rest = lib.str_to_ints(line) speed, time, rest = ints(line)
rds.append([speed, time, rest, 0, 0, 0]) deers.append([0, speed, time, rest, time, 0])
speed *= 1000
dist = 0 points = defaultdict(int)
ctime = 0 for _ in range(2503):
while ctime < gtime: max_dists = defaultdict(list)
t = min(time, gtime - ctime) for i, deer in enumerate(deers):
dist += (speed * t) dist, speed, time, rest, time_left, rest_left = deer
ctime += time if time_left > 0:
ctime += rest time_left -= 1
max_dist = max(dist, max_dist) 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: deer[0] = dist
print(max_dist // 1000) deer[4] = time_left
deer[5] = rest_left
scores = [0 for _ in range(len(rds))] max_deers = max(max_dists.items())
for _ in range(gtime): for di in max_deers[1]:
for i in range(len(rds)): points[di] += 1
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_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,41 +1,41 @@
import sys with open("i19.txt", "r") as f:
from random import shuffle data = f.read()
data = open(0).read().strip()
eqs = [] eqs = []
text = "" molecule = ""
for line in data.splitlines(): for line in data.splitlines():
if "=>" in line: if "=>" in line:
lhs, rhs = line.split(" => ") lhs, rhs = line.split(" => ")
eqs.append((lhs, rhs)) eqs.append((lhs, rhs))
elif line: elif line:
text = line molecule = line.strip()
molecules = set() molecules = set()
for lhs, rhs in eqs: for lhs, rhs in eqs:
for i in range(len(text)): for i in range(len(molecule)):
f, t = text[:i], text[i:] f, t = molecule[:i], molecule[i:]
if t.startswith(lhs): if t.startswith(lhs):
n = f + t.replace(lhs, rhs, 1) n = f + t.replace(lhs, rhs, 1)
molecules.add(n) molecules.add(n)
print(len(molecules)) print(len(molecules))
# m = float("inf")
# for _ in range(100000): # It seems like the problem input is specifically designed so that reversing
# getout = False # the molecule back to 'e' works by simply replacing RHS with LHS continuously.
# c = str(text) # Intuitively, it seems like this should not work in many cases or at least not
# for i in range(10**4): # result in the shortest sequence. Probably I am just salty that it took me so
# if i % 1000 == 0: # long to find this trivial solution.
# shuffle(eqs) x = molecule
# for lhs, rhs in eqs: count = 0
# if rhs in c: while len(x) > 1:
# c = c.replace(rhs, lhs, 1) old_len = len(x)
# if c == "e": for lhs, rhs in eqs:
# if i < m: if rhs in x:
# m = i x = x.replace(rhs, lhs, 1)
# print(i) count += 1
# getout = True break
# if getout: else:
# break break
# print(m)
assert x == 'e'
print(count)

View File

@@ -1,19 +1,17 @@
from lib import * from lib import get_data, ints
data = open(0).readlines() data = get_data(__file__)
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)
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) print(a)

View File

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

40
2015/d23.py Normal file
View File

@@ -0,0 +1,40 @@
insts = []
with open("i23.txt", "r") as f:
for line in f:
args = line.strip().replace(",", "").split()
insts.append(args)
part_2 = True
i = 0
regs = {"a": 0, "b": 0}
if part_2:
regs["a"] = 1
while i < len(insts):
inst = insts[i]
match inst:
case ["inc", reg]:
regs[reg] += 1
case ["hlf", reg]:
regs[reg] //= 2
case ["tpl", reg]:
regs[reg] *= 3
case ["jio", reg, offset]:
if regs[reg] == 1:
i += int(offset)
continue
case ["jie", reg, offset]:
offset = int(offset)
if regs[reg] % 2 == 0:
i += int(offset)
continue
case ["jmp", offset]:
i += int(offset)
continue
case _:
print(inst)
assert False
i += 1
print(regs["b"])

46
2015/d24.py Normal file
View File

@@ -0,0 +1,46 @@
import sys
from itertools import combinations
with open("i24.txt", "r") as f:
weights = list(map(int, f.readlines()))
total_sum = sum(weights)
indices = list(range(len(weights)))
lowest = None
def find(part_1=True):
if part_1:
target_weight = total_sum // 3
else:
target_weight = total_sum // 4
for group_1_size in range(1, len(weights) - 1):
for group_1_indices in combinations(indices, group_1_size):
group_1_weight = sum(weights[i] for i in group_1_indices)
if not group_1_weight == target_weight:
continue
remaining_indices = list(set(indices) - set(group_1_indices))
for group_2_size in range(1, len(remaining_indices) - 1):
for group_2_indices in combinations(remaining_indices, group_2_size):
group_2_weight = sum(weights[i] for i in group_2_indices)
if not group_2_weight == target_weight:
continue
if part_1 :
r = 1
for i in group_1_indices:
r *= weights[i]
return r
else:
remaining_indices = list(set(indices) - set(group_1_indices))
for group_3_size in range(1, len(remaining_indices)- 1):
for group_3_indices in combinations(remaining_indices, group_3_size):
group_3_weight = sum(weights[i] for i in group_3_indices)
if not group_3_weight == target_weight:
continue
r = 1
for i in group_1_indices:
r *= weights[i]
return r
print(find(True))
print(find(False))

19
2015/d25.py Normal file
View File

@@ -0,0 +1,19 @@
import sys
from lib import str_to_ints
with open("i25.txt", "r") as f:
target_row, target_col = str_to_ints(f.read())
x = 20151125
m = 252533
d = 33554393
for start_row in range(1, 10000):
row, col = start_row, 1
while row > 0:
if row == target_row and col == target_col:
print(x)
sys.exit(0)
x = (x * m) % d
row -= 1
col += 1

View File

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

View File

@@ -1,22 +1,15 @@
from lib import * from lib import get_data
import hashlib import hashlib
part_1 = True data = get_data(__file__).strip()
if part_1: for digits in [5, 6]:
digits = 5 for i in range(10**9):
else: text = data + str(i)
digits = 6 md5_hash = hashlib.md5(text.encode()).hexdigest()
for c in md5_hash[:digits]:
data = open(0).read().strip() if c != "0":
break
for i in range(10**9): else:
text = data + str(i) print(i)
md5_hash = hashlib.md5(text.encode()).hexdigest()
for c in md5_hash[:digits]:
if c != "0":
break 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 def is_nice(line):
for c in line: # It contains at least three vowels (aeiou only), like aei, xazegov, or aeiouaeiouaeiou.
if c == prev: vc = 0
break for v in "aeiou":
prev = c vc += line.count(v)
else: if vc < 3:
continue return False
contains = False # It contains at least one letter that appears twice in a row, like xx,
ss = ["ab", "cd", "pq", "xy"] # abcdde (dd), or aabbccdd (aa, bb, cc, or dd).
for s in ss: for i in range(len(line) - 1):
if s in line: if line[i] == line[i + 1]:
contains = True break
else:
return False
if contains: # It does not contain the strings ab, cd, pq, or xy, even if they are
continue # part of one of the other requirements.
res += 1 for i in range(len(line) - 1):
print(res) cc = "".join(line[i : i + 2])
else: if cc in ["ab", "cd", "pq", "xy"]:
res = 0 return False
for line in data.splitlines(): return True
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
for i in range(0, len(line) - 2):
if line[i] == line[i + 2]: def is_nice_2(line):
break good = False
else: for i in range(len(line) - 1):
continue cc = "".join(line[i : i + 2])
res += 1 for j in range(i + 2, len(line) - 1):
print(res) 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() data = get_data(__file__)
part_2 = True
lights = [[0 for _ in range(1000)] for _ in range(1000)]
ons = set()
ons2 = DD(int)
for line in data.splitlines(): 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 x in range(x1, x2 + 1):
for y in range(y1, y2 + 1): for y in range(y1, y2 + 1):
if part_2: if "off" in line:
if "on" in line: if (x, y) in ons:
lights[x][y] += 1 ons.remove((x, y))
elif "off" in line: ons2[(x, y)] = max(ons2[(x, y)] - 1, 0)
lights[x][y] = max(0, lights[x][y] - 1) 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: else:
lights[x][y] += 2 ons.add((x, y))
ons2[(x, y)] += 2
else: else:
if "on" in line: assert False
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
res = 0 print(len(ons))
for row in lights: print(sum(ons2.values()))
res += sum(row)
print(res)

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: def run(wires={}):
for line in data.splitlines(): def get(a):
lhs, rhs = line.split(" -> ") try:
if part_2 and rhs == "b": return int(a)
continue except ValueError:
lhs = lhs.strip() pass
if "NOT" in lhs: if a in wires:
op, op1 = lhs.split(" ") return wires[a]
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
else: else:
try: return None
lhs = int(lhs)
gates[rhs] = lhs
except ValueError:
if lhs in gates:
gates[rhs] = gates[lhs]
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() data = get_data(__file__)
part_1 = False
if part_1: r1 = re.compile(r"\\x[0-9a-f][0-9a-f]")
r1 = re.compile(r"\\x[0-9a-f][0-9a-f]") r2 = re.compile(r"\\\\")
r2 = re.compile(r"\\\\") r3 = re.compile(r"\\\"")
r3 = re.compile(r"\\\"")
enc, mem = 0, 0 enc, mem = 0, 0
for line in data.splitlines(): for line in data.splitlines():
mem += len(line) mem += len(line)
line = r1.sub("^", line) line = r1.sub("^", line)
line = r2.sub("^", line) line = r2.sub("^", line)
line = r3.sub("^", line) line = r3.sub("^", line)
enc += len(line) - 2 enc += len(line) - 2
print(mem - enc) print(mem - enc)
else:
ori, enc = 0, 0 ori, enc = 0, 0
for line in data.splitlines(): for line in data.splitlines():
ori += len(line) ori += len(line)
line = line.replace("\\", "\\\\") line = line.replace("\\", "\\\\")
line = line.replace("\"", "\\\"") line = line.replace('"', '\\"')
enc += len(line) + 2 enc += len(line) + 2
print(enc - ori) print(enc - ori)

View File

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

32
2016/d18.py Normal file
View File

@@ -0,0 +1,32 @@
from lib import *
row = open(0).read().strip()
def is_trap(s):
assert len(s) == 3
match s:
case "^^.":
return "^"
case ".^^":
return "^"
case "^..":
return "^"
case "..^":
return "^"
case _:
return "."
# 400_000 is still easily bruteforcible. If it was a much larget number, we
# would have to cache the rows till we get a repeated row. We would then
# memorize the number of safe tiles from repeated row to repeated row and jump
# forward by multiples of that amount to reach much higher numbers.
r = 0
for _ in range(400_000):
r += row.count(".")
nrow = is_trap("." + row[:2])
for i in range(1, len(row) - 1):
nrow += is_trap(row[i-1:i+2])
nrow += is_trap(row[-2:] + ".")
row = nrow
print(r)

87
2016/d19.py Normal file
View File

@@ -0,0 +1,87 @@
from dataclasses import dataclass
from typing import Optional
def part_1(data):
elf_count = int(data)
elves = [i for i in range(1, elf_count + 1)]
while len(elves) > 1:
is_odd = len(elves) % 2 == 1
elves = [elves[i] for i in range(0, len(elves), 2)]
if is_odd and len(elves) > 1:
elves = elves[1:]
print(elves[0])
def part_2(data):
elf_count = int(data)
power_3 = 3
while power_3 * 3 < elf_count:
power_3 *= 3
elf_count -= power_3
print(elf_count)
def main():
data = open(0).read().strip()
part_1(data)
part_2(data)
if __name__ == "__main__":
main()
def part_2_naiv(data):
@dataclass
class Elf:
id: int
prev: Optional["Elf"]
next: Optional["Elf"]
def delete(self):
if self.prev is not None:
self.prev.next = self.next
if self.next is not None:
self.next.prev = self.prev
self.prev = None
self.next = None
def all(self):
elf = self
elves = [elf]
while elf.next is not None and elf.next.id != self.id:
elf = elf.next
elves.append(elf)
return elves
def forward(self, n):
current = self
for _ in range(n):
assert current.next is not None
current = current.next
assert current.id != self.id
return current
def __repr__(self):
assert self.prev is not None and self.next is not None
return f"{self.prev.id} <- {self.id} -> {self.next.id}"
elf_count = int(data)
# elf_count = 5
elves = [Elf(i, None, None) for i in range(1, elf_count + 1)]
for i in range(len(elves)):
elves[i].prev = elves[(i - 1) % len(elves)]
elves[i].next = elves[(i + 1) % len(elves)]
remaining_elves = elf_count
current_elf = elves[0]
while remaining_elves > 1:
assert current_elf is not None
# print(current_elf.all())
current_elf.forward(remaining_elves // 2).delete()
current_elf = current_elf.next
remaining_elves -= 1
assert current_elf is not None
print(current_elf.id)

48
2016/d20.py Normal file
View File

@@ -0,0 +1,48 @@
def part_1(data):
ranges = []
for line in data.splitlines():
l, h = list(map(int, line.split("-")))
ranges.append((l, h))
ranges.sort()
merged = list(ranges[0])
for i in range(1, len(ranges)):
current = ranges[i]
if current[0] <= merged[1] + 1:
merged[1] = max(current[1], merged[1])
else:
print(merged[1] + 1)
return
def part_2(data):
ranges = []
for line in data.splitlines():
l, h = list(map(int, line.strip().split("-")))
ranges.append((l, h))
ranges.sort()
blocked_merged = [list(ranges[0])]
for block in ranges:
merged = blocked_merged[-1]
if block[0] <= merged[1] + 1:
merged[1] = max(block[1], merged[1])
else:
blocked_merged.append(list(block))
total = 2**32
for lo, hi in blocked_merged:
total -= (hi - lo + 1)
print(total)
def main():
data = open(0).read().strip()
part_1(data)
part_2(data)
if __name__ == "__main__":
main()

66
2016/d21.py Normal file
View File

@@ -0,0 +1,66 @@
from itertools import permutations
def part_1(data, text):
text = list(text)
for line in data.splitlines():
words = line.split()
# print(text)
# print(words)
match words:
case ["move", "position", a, "to", "position", b]:
l = text.pop(int(a))
text.insert(int(b), l)
case ["rotate", "right", a, "steps"] | ["rotate", "right", a, "step"]:
a = int(a)
ntext = list(text)
for i in range(len(text)):
ntext[(i + a) % len(text)] = text[i]
text = ntext
case ["rotate", "left", a, "steps"] | ["rotate", "left", a, "step"]:
a = int(a)
ntext = list(text)
for i in range(len(text)):
ntext[(i - a) % len(text)] = text[i % len(text)]
text = ntext
case ["swap", "letter", a, "with", "letter", b]:
ia = text.index(a)
ib = text.index(b)
text[ia], text[ib] = text[ib], text[ia]
case ["swap", "position", a, "with", "position", b]:
ia, ib = int(a), int(b)
text[ia], text[ib] = text[ib], text[ia]
case ["reverse", "positions", a, "through", b]:
a, b = int(a), int(b)
assert b > a
text[a:b + 1] = reversed(text[a:b + 1])
case ["rotate", "based", "on", "position", "of", "letter", c]:
i = text.index(c)
a = 1 + i + (1 if i >= 4 else 0)
ntext = list(text)
for i in range(len(text)):
ntext[(i + a) % len(text)] = text[i % len(text)]
text = ntext
case _:
assert False
return "".join(text)
def part_2(data):
pw = "fbgdceah"
for p in permutations(pw):
if part_1(data, p) == pw:
print("".join(p))
return
def main():
data = open(0).read().strip()
print(part_1(data, "abcdefgh"))
part_2(data)
if __name__ == "__main__":
main()

89
2016/d22.py Normal file
View File

@@ -0,0 +1,89 @@
from lib import str_to_ints
from dataclasses import dataclass
from collections import defaultdict
data = """ Filesystem Size Used Avail Use%
/dev/grid/node-x0-y0 10T 8T 2T 80%
/dev/grid/node-x0-y1 11T 6T 5T 54%
/dev/grid/node-x0-y2 32T 28T 4T 87%
/dev/grid/node-x1-y0 9T 7T 2T 77%
/dev/grid/node-x1-y1 8T 0T 8T 0%
/dev/grid/node-x1-y2 11T 7T 4T 63%
/dev/grid/node-x2-y0 10T 6T 4T 60%
/dev/grid/node-x2-y1 9T 8T 1T 88%
/dev/grid/node-x2-y2 9T 6T 3T 66%
"""
@dataclass
class Node:
id: int
x: int
y: int
size: int
used: int
avail: int
usep: int
def get_nodes(data):
return [Node(i, *str_to_ints(line)) for i, line in enumerate(data.splitlines()[2:])]
def part_1(data):
c = 0
nodes = get_nodes(data)
for a in nodes:
for b in nodes:
if a is not b and a.used != 0 and a.used <= b.avail:
c += 1
print(c)
def part_2(data):
nodes = get_nodes(data)
empty = None
for n in nodes:
if n.usep < 50:
assert empty is None
empty = (n.x, n.y)
assert empty is not None
grid = defaultdict(dict)
for n in nodes:
grid[n.x][n.y] = n
cols = len(grid)
steps = 0
steps += empty[1] # move space to y=0
steps += ((cols - 2) - empty[0]) # move space to x=cols-1
# shuffle target cell to (0, 0) via empty space
steps += ((cols - 2) * 5)
steps += 1
# Account for the fact that there is a "barrier" through which we cannot
# move the space. This can be seen by uncommenting the grid print code
# below. Might have been better to code a proper search from the beginning.
# Clearly he enjoyed setting a bit of a trap there.
steps += 15 - 3
# for y in range(rows):
# for x in range(cols):
# print(f"{grid[x][y].used:2} ", end='')
# print()
print(steps)
def main():
data = open(0).read().strip()
part_1(data)
part_2(data)
if __name__ == "__main__":
main()

87
2016/d23.py Normal file
View File

@@ -0,0 +1,87 @@
from math import factorial
def solve(data, part_2=False):
REGS = "abcd"
regs = {c: 0 for c in REGS}
if part_2:
print(factorial(12) + (91 * 85))
return
else:
regs["a"] = 7
insts = data.splitlines()
i = 0
inst_count = 0
while i < len(insts):
inst_count += 1
# print(i, regs)
parts = insts[i].split()
cmd = parts[0]
if cmd == "cpy":
if parts[1] in REGS:
regs[parts[2]] = regs[parts[1]]
else:
regs[parts[2]] = int(parts[1])
elif cmd == "jnz":
val = 0
if parts[1] in REGS:
val = regs[parts[1]]
else:
val = int(parts[1])
if val != 0:
if parts[2] in REGS:
i += regs[parts[2]]
else:
i += int(parts[2])
continue
elif cmd == "inc":
if insts[i + 1] == "dec d" and insts[i + 2] == "jnz d -2":
regs[parts[1]] += regs["d"]
regs["d"] = 0
i += 3
continue
regs[parts[1]] += 1
elif cmd == "dec":
regs[parts[1]] -= 1
elif cmd == "tgl":
off = 0
if parts[1] in REGS:
off = regs[parts[1]]
else:
off = int(parts[1])
addr = i + off
if addr < len(insts):
parts = insts[addr].split()
if len(parts) == 2:
if parts[0] == "inc":
parts[0] = "dec"
else:
parts[0] = "inc"
insts[addr] = " ".join(parts)
elif len(parts) == 3:
if parts[0] == "jnz":
parts[0] = "cpy"
else:
parts[0] = "jnz"
insts[addr] = " ".join(parts)
else:
assert False
else:
assert False
i += 1
print(regs["a"])
def main():
data = open(0).read().strip()
solve(data)
solve(data, True)
if __name__ == "__main__":
main()

61
2016/d24.py Normal file
View File

@@ -0,0 +1,61 @@
from collections import deque, defaultdict
from itertools import permutations
from lib import Grid2D, INF
def shortest_path(grid, start, end):
seen = set()
open = deque([(start, 0)])
while open:
current, steps = open.popleft()
if current in seen:
continue
seen.add(current)
if current == end:
return steps
for nb in grid.neighbors_ort(current):
if grid[nb] == "#":
continue
open.append((nb, steps + 1))
return None
def solve(data, part_2=False):
g = Grid2D(data)
# g.print()
start = g.find("0")[0]
points = g.find_not("#.")
shortest = defaultdict(defaultdict)
for i in range(len(points)):
for j in range(i + 1, len(points)):
a, b = points[i], points[j]
s = shortest_path(g, a, b)
assert s is not None
shortest[a][b] = s
shortest[b][a] = s
# Brute force all combinations.
points.remove(start)
dmin = INF
for p in permutations(points):
d = 0
d += shortest[start][p[0]]
for i in range(len(p) - 1):
d += shortest[p[i]][p[i + 1]]
if part_2:
d += shortest[p[-1]][start]
dmin = min(d, dmin)
print(dmin)
return
def main():
data = open(0).read().strip()
solve(data)
solve(data, True)
if __name__ == "__main__":
main()

60
2016/d25.py Normal file
View File

@@ -0,0 +1,60 @@
def solve(data):
REGS = "abcd"
goal = [0, 1, 0, 1, 0, 1, 0, 1]
for a in range(0, 1000):
insts = data.splitlines()
regs = {c: 0 for c in REGS}
regs["a"] = a
outs = []
i = 0
inst_count = 0
while i < len(insts):
inst_count += 1
if inst_count > 100_000:
break
parts = insts[i].split()
cmd = parts[0]
if cmd == "cpy":
if parts[1] in "abcd":
regs[parts[2]] = regs[parts[1]]
else:
regs[parts[2]] = int(parts[1])
elif cmd == "out":
if parts[1] in "abcd":
v = regs[parts[1]]
else:
v = int(parts[1])
outs.append(v)
if len(outs) == len(goal):
if outs == goal:
print(a)
return
else:
# print(outs)
pass
elif cmd == "jnz":
val = 0
if parts[1] in "abcd":
val = regs[parts[1]]
else:
val = int(parts[1])
if val != 0:
i += int(parts[2])
continue
elif cmd == "inc":
regs[parts[1]] += 1
elif cmd == "dec":
regs[parts[1]] -= 1
else:
assert False
i += 1
def main():
data = open(0).read().strip()
solve(data)
if __name__ == "__main__":
main()

64
2017/d10.py Normal file
View File

@@ -0,0 +1,64 @@
from lib import str_to_ints
def part_1(data):
ns = list(range(256))
lenghts = str_to_ints(data)
i = 0
skip_size = 0
for length in lenghts:
a, b = i, (i + length) % len(ns)
if a <= b:
ns[a:b] = reversed(ns[a:b])
else:
rev = list(reversed(ns[a:] + ns[:b]))
ns[a:] = rev[:len(ns) - a]
ns[:b] = rev[-b:]
i = (i + length + skip_size) % len(ns)
skip_size += 1
print(ns[0] * ns[1])
def part_2(data):
ns = list(range(256))
lenghts = list(map(ord, data)) + [17, 31, 73, 47, 23]
rounds = 64
i = 0
skip_size = 0
for _ in range(rounds):
for length in lenghts:
assert length < 256
a, b = i, (i + length) % len(ns)
if a <= b:
ns[a:b] = reversed(ns[a:b])
else:
rev = list(reversed(ns[a:] + ns[:b]))
ns[a:] = rev[:len(ns) - a]
if b != 0:
ns[:b] = rev[-b:]
i = (i + length + skip_size) % len(ns)
assert len(ns) == 256
skip_size += 1
out = ""
for i in range(0, len(ns), 16):
r = 0
for j in range(i, i + 16):
r ^= ns[j]
out += f"{r:02x}"
return out
def main():
data = open(0).read().strip()
part_1(data)
assert part_2("") == "a2582a3a0e66e6e86e3812dcb672a272"
assert part_2("AoC 2017") == "33efeb34ea91902bb2f59c9920caa6cd"
assert part_2("1,2,3") == "3efbe78a8d82f29979031a4aa0b16a9d"
assert part_2("1,2,4") =="63960835bcdc130f0b66d7ff4f6a5a8e"
print(part_2(data))
if __name__ == "__main__":
main()

52
2017/d11.py Normal file
View File

@@ -0,0 +1,52 @@
from lib import add2
from math import ceil
DIR = {
"n": (-2, 0),
"s": (2, 0),
"e": (0, 1),
"w": (0, -1),
"ne": (-1, 1),
"nw": (-1, -1),
"se": (1, 1),
"sw": (1, -1),
}
def steps_to_zero(pos: tuple[int, int]):
c = list(map(abs, pos))
steps = c[1]
c[0] -= c[1]
if c[0] > 0:
steps += ceil(c[0] / 2)
return steps
def part_1(data):
c = (0, 0)
for d in data.split(","):
c = add2(c, DIR[d])
return steps_to_zero(c)
def part_2(data):
c = (0, 0)
steps_max = 0
for d in data.split(","):
c = add2(c, DIR[d])
steps_max = max(steps_to_zero(c), steps_max)
return steps_max
def main():
data = open(0).read().strip()
print(part_1(data))
print(part_2(data))
assert part_1("ne,ne,ne") == 3
assert part_1("ne,ne,sw,sw") == 0
assert part_1("ne,ne,s,s") == 2
assert part_1("se,sw,se,sw,sw") == 3
if __name__ == "__main__":
main()

56
2017/d12.py Normal file
View File

@@ -0,0 +1,56 @@
from lib import str_to_ints
from collections import defaultdict
def get_graph(data):
g = defaultdict(set)
for line in data.splitlines():
fs = str_to_ints(line)
l = fs[0]
for r in fs[1:]:
g[l].add(r)
g[r].add(l)
return g
def part_1(data):
g = get_graph(data)
to_visit = [0]
seen = set()
while to_visit:
c = to_visit.pop()
if c in seen:
continue
seen.add(c)
for nb in g[c]:
to_visit.append(nb)
print(len(seen))
def part_2(data):
g = get_graph(data)
group_count = 0
seen = set()
for c in g.keys():
if c in seen:
continue
group_count += 1
to_visit = [c]
while to_visit:
c = to_visit.pop()
if c in seen:
continue
seen.add(c)
for nb in g[c]:
to_visit.append(nb)
print(group_count)
def main():
data = open(0).read().strip()
part_1(data)
part_2(data)
if __name__ == "__main__":
main()

52
2017/d13.py Normal file
View File

@@ -0,0 +1,52 @@
from lib import str_to_ints
def part_1(data):
layer_to_depth = {}
for line in data.splitlines():
a, b = str_to_ints(line)
layer_to_depth[a] = b
layer_to_pos = {l: 0 for l in layer_to_depth.keys()}
layer_to_dir = {l: -1 for l in layer_to_depth.keys()}
target = max(list(layer_to_depth.keys()))
r = 0
layer = 0
while layer <= target:
if layer in layer_to_pos and layer_to_pos[layer] == 0:
r += (layer * layer_to_depth[layer])
for k in layer_to_pos.keys():
if layer_to_pos[k] == 0 or (layer_to_pos[k] + 1) == layer_to_depth[k]:
layer_to_dir[k] *= -1
layer_to_pos[k] += layer_to_dir[k]
layer += 1
print(r)
def part_2(data):
eqs = []
for line in data.splitlines():
layer, depth = str_to_ints(line)
period = (depth - 1) * 2
eqs.append((layer, period))
for delay in range(1, 1_000_000_000):
for layer, period in eqs:
if (delay + layer) % period == 0:
break
else:
print(delay)
return
def main():
data = open(0).read().strip()
part_1(data)
part_2(data)
if __name__ == "__main__":
main()

72
2017/d14.py Normal file
View File

@@ -0,0 +1,72 @@
from lib import Grid2D
from d10 import part_2 as hash
def to_bin(x):
assert len(x) == 1
xv = int(x, 16)
c = 1
r = ""
for _ in range(4):
if c & xv > 0:
r = "1" + r
else:
r = "0" + r
c = (c << 1)
return r
def to_bits(xs):
r = ""
for x in xs:
r += to_bin(x)
return r
def part_1(data):
r = 0
for i in range(128):
s = f"{data}-{i}"
x = hash(s)
b = to_bits(x)
r += b.count("1")
print(r)
def part_2(data):
grid = ""
for i in range(128):
s = f"{data}-{i}"
x = hash(s)
grid += to_bits(x)
grid += "\n"
g = Grid2D(grid)
one_group_count = 0
seen_ones = set()
all_ones = g.find("1")
for c in all_ones:
if c in seen_ones:
continue
one_group_count += 1
current_group = [c]
while current_group:
c = current_group.pop()
if c in seen_ones:
continue
seen_ones.add(c)
for nb in g.neighbors_ort(c):
if g[nb] == "1":
current_group.append(nb)
print(one_group_count)
def main():
data = open(0).read().strip()
part_1(data)
part_2(data)
if __name__ == "__main__":
main()

59
2017/d15.py Normal file
View File

@@ -0,0 +1,59 @@
from lib import str_to_int
def part_1(data):
fa = 16807
fb = 48271
m = 2147483647
xs = []
for line in data.splitlines():
xs.append(str_to_int(line))
a, b = xs
c = 0
for _ in range(40_000_000):
a = (a * fa) % m
b = (b * fb) % m
if (a & 0xffff) == (b & 0xffff):
c += 1
print(c)
def gen(a, f, m, d):
while True:
a = (a * f) % m
if a % d == 0:
yield a
def part_2(data):
fa = 16807
fb = 48271
m = 2147483647
xs = []
for line in data.splitlines():
xs.append(str_to_int(line))
a, b = xs
xs = gen(a, fa, m, 4)
ys = gen(b, fb, m, 8)
c = 0
for _ in range(5_000_000):
a = next(xs)
b = next(ys)
if (a & 0xffff) == (b & 0xffff):
c += 1
print(c)
def main():
data = open(0).read().strip()
part_1(data)
part_2(data)
if __name__ == "__main__":
main()

60
2017/d16.py Normal file
View File

@@ -0,0 +1,60 @@
from lib import LETTERS_LOWER, str_to_int, str_to_ints
def one_cycle(xs, insts):
for inst in insts:
if inst.startswith("s"):
v = str_to_int(inst)
if v != 0:
xs = xs[-v:] + xs[:len(xs) - v]
elif inst.startswith("x"):
a, b = str_to_ints(inst)
xs[a], xs[b] = xs[b], xs[a]
elif inst.startswith("p"):
la, lb = inst[1], inst[3]
a, b = xs.index(la), xs.index(lb)
xs[a], xs[b] = xs[b], xs[a]
else:
assert False
return xs
def part_1(data):
xs = list(LETTERS_LOWER[:16])
insts = data.split(",")
xs = one_cycle(xs, insts)
r = "".join(xs)
print(r)
def part_2(data):
repeat = 10**9
xs = list(LETTERS_LOWER[:16])
insts = data.split(",")
cycle = 0
seen = {}
for i in range(repeat):
xs = one_cycle(xs, insts)
xst = tuple(xs)
if not xst in seen:
seen[xst] = i
else:
cycle = (i - seen[xst])
break
xs = list(LETTERS_LOWER[:16])
repeat %= cycle
for i in range(repeat):
xs = one_cycle(xs, insts)
r = "".join(xs)
print(r)
def main():
data = open(0).read().strip()
part_1(data)
part_2(data)
if __name__ == "__main__":
main()

39
2017/d17.py Normal file
View File

@@ -0,0 +1,39 @@
def part_1(data):
target = 2017
steps = int(data)
xs = [0]
i = 0
for n in range(1, target + 1):
i = (i + steps) % len(xs)
xs.insert(i + 1, n)
i += 1
for j in range(len(xs)):
if xs[j] == target:
print(xs[j + 1])
return
def part_2(data, m=50_000_000):
steps = int(data)
len_xs = 1
last_zero = None
i = 0
for n in range(1, m + 1):
i = (i + steps) % len_xs
if i == 0:
last_zero = n
len_xs += 1
i += 1
return last_zero
def main():
data = open(0).read().strip()
part_1(data)
assert part_2(data, 1000) == 531
assert part_2(data, 10000) == 2616
print(part_2(data))
if __name__ == "__main__":
main()

109
2017/d18.py Normal file
View File

@@ -0,0 +1,109 @@
from lib import LETTERS_LOWER
from collections import defaultdict, deque
def get_value(regs, val):
if val in LETTERS_LOWER:
return regs[val]
return int(val)
def part_1(data):
regs = defaultdict(int)
insts = data.splitlines()
last_sound = None
pc = 0
while pc <= len(insts):
cmds = insts[pc].split()
match cmds:
case ["set", reg, val]:
val = get_value(regs, val)
regs[reg] = val
case ["mul", reg, val]:
val = get_value(regs, val)
regs[reg] = regs[reg] * val
case ["add", reg, val]:
val = get_value(regs, val)
regs[reg] = regs[reg] + val
case ["mod", reg, val]:
val = get_value(regs, val)
regs[reg] = regs[reg] % val
case ["rcv", val]:
if get_value(regs, val) != 0:
print(last_sound)
return
case ["snd", val]:
last_sound = get_value(regs, val)
case ["jgz", cnd, off]:
if get_value(regs, cnd) > 0:
pc += get_value(regs, off) # XXX?
continue
case _:
print(cmds)
assert False
pc += 1
def part_2(data):
progs = []
insts = data.splitlines()
prog1_counter = 0
for i in [0, 1]:
regs = defaultdict(int)
regs["p"] = i
pc = 0
progs.append([pc, regs, False, deque()])
while not all(prog[2] for prog in progs):
for prog in progs:
pc, regs, waiting, queue = prog
cmds = insts[pc].split()
match cmds:
case ["set", reg, val]:
val = get_value(regs, val)
regs[reg] = val
case ["mul", reg, val]:
val = get_value(regs, val)
regs[reg] = regs[reg] * val
case ["add", reg, val]:
val = get_value(regs, val)
regs[reg] = regs[reg] + val
case ["mod", reg, val]:
val = get_value(regs, val)
regs[reg] = regs[reg] % val
case ["rcv", reg]:
if queue:
val = queue.popleft()
regs[reg] = val
else:
prog[2] = True
pc -= 1
case ["snd", val]:
val = get_value(regs, val)
if prog == progs[0]:
other_prog = progs[1]
else:
prog1_counter += 1
other_prog = progs[0]
other_prog[3].append(val)
case ["jgz", cnd, off]:
if get_value(regs, cnd) > 0:
pc += get_value(regs, off) # XXX?
pc -= 1
case _:
print(cmds)
assert False
prog[0] = pc + 1
print(prog1_counter)
def main():
data = open(0).read().strip()
part_1(data)
part_2(data)
if __name__ == "__main__":
main()

50
2017/d19.py Normal file
View File

@@ -0,0 +1,50 @@
from lib import Grid2D, LETTERS_UPPER, add2, sub2
def part_1(data):
g = Grid2D(data)
g.print()
current = None
for c in range(g.n_cols):
if g[(0, c)] == "|":
current = (0, c)
current_dir = Grid2D.S
letters = ""
done = False
steps = 0
while not done:
while g[current] != " ":
current = add2(current, current_dir)
steps += 1
if g[current] in LETTERS_UPPER:
letter = g[current]
if letter in letters:
done = True
break
letters += letter
if g[current] == "+":
break
next_field = None
for nb in g.neighbors_ort(current):
if nb != sub2(current, current_dir) and (g[nb] in "-|" or g[nb] in LETTERS_UPPER):
next_field = nb
if next_field is None:
done = True
else:
current_dir = sub2(next_field, current)
print(letters)
print(steps)
def main():
data = open(0).read()
part_1(data)
if __name__ == "__main__":
main()

55
2017/d20.py Normal file
View File

@@ -0,0 +1,55 @@
from lib import str_to_ints
from collections import defaultdict
def part_1(data):
ps = []
for i, line in enumerate(data.splitlines()):
ps.append(str_to_ints(line) + [i])
for _ in range(10_000):
for p in ps:
p[3] += p[6]
p[4] += p[7]
p[5] += p[8]
p[0] += p[3]
p[1] += p[4]
p[2] += p[5]
ps.sort(key=lambda p: abs(p[0]) + abs(p[1]) + abs(p[2]))
print(ps[0][-1])
def part_2(data):
ps = []
for i, line in enumerate(data.splitlines()):
ps.append(str_to_ints(line) + [i])
for _ in range(10_000):
poss = defaultdict(list)
for p in ps:
p[3] += p[6]
p[4] += p[7]
p[5] += p[8]
p[0] += p[3]
p[1] += p[4]
p[2] += p[5]
poss[(p[0], p[1], p[2])].append(p)
for pts in poss.values():
if len(pts) > 1:
for p in pts:
ps.remove(p)
print(len(ps))
def main():
data = open(0).read()
part_1(data)
part_2(data)
if __name__ == "__main__":
main()

110
2017/d21.py Normal file
View File

@@ -0,0 +1,110 @@
IMAGE = """.#.
..#
###"""
def flipv(pattern):
return tuple(map(lambda row: tuple(reversed(row)), pattern))
def fliph(pattern):
return tuple(reversed(pattern))
def rot90(pattern):
return tuple(map(tuple, map(reversed, tuple(zip(*pattern)))))
def parse_rule(line: str) -> dict:
lhs, rhs = line.split(" => ")
rules = {}
lhs = tuple(map(tuple, lhs.split("/")))
rhs = list(map(list, rhs.split("/")))
rules[lhs] = rhs
rules[rot90(lhs)] = rhs
rules[rot90(rot90((lhs)))] = rhs
rules[rot90(rot90(rot90((lhs))))] = rhs
rules[flipv(lhs)] = rhs
rules[fliph(lhs)] = rhs
rules[rot90(flipv(lhs))] = rhs
rules[rot90(rot90(flipv(lhs)))] = rhs
rules[rot90(rot90(rot90(flipv(lhs))))] = rhs
rules[rot90(fliph(lhs))] = rhs
rules[rot90(rot90(fliph(lhs)))] = rhs
rules[rot90(rot90(rot90(fliph(lhs))))] = rhs
return rules
def print_image(image):
for row in image:
print("".join(row))
def slice_get(matrix, row, col, size):
r = []
for ri in range(row, row + size):
r.append(matrix[ri][col:col+size])
return r
def slice_set(matrix, row, col, new):
for ri, r in enumerate(new):
matrix[ri + row][col:col + len(r)] = r
def slice_append(matrix, slice):
for ri, row in enumerate(slice):
for c in row:
matrix[-len(slice) + ri].append(c)
def part_1(data, iterations=5):
rules = {}
image = list(map(list, IMAGE.splitlines()))
for line in data.splitlines():
line = line.strip()
for k, v in parse_rule(line).items():
assert (k not in rules) or rules[k] == v
rules[k] = v
for _ in range(iterations):
len_image = len(image)
new_image = []
if len_image % 2 == 0:
for row in range(0, len(image), 2):
for _ in range(3):
new_image.append([])
for col in range(0, len(image[0]), 2):
slice = tuple(map(tuple, slice_get(image, row, col, 2)))
new_slice = rules[slice]
slice_append(new_image, new_slice)
else:
for row in range(0, len(image), 3):
for _ in range(4):
new_image.append([])
for col in range(0, len(image[0]), 3):
slice = tuple(map(tuple, slice_get(image, row, col, 3)))
new_slice = rules[slice]
slice_append(new_image, new_slice)
image = new_image
count = 0
for row in image:
count += row.count("#")
print(count)
def main():
data = open(0).read()
part_1(data)
part_1(data, 18)
if __name__ == "__main__":
main()

71
2017/d22.py Normal file
View File

@@ -0,0 +1,71 @@
from lib import Grid2D, add2
def part_1(data):
steps = 10_000
g = Grid2D(data)
# g.print()
dirs = [g.N, g.E, g.S, g.W]
pos = (g.n_rows // 2, g.n_cols // 2)
dir = g.N
inf = set(g.find("#"))
burst_inf = 0
for _ in range(steps):
if pos in inf:
# turn right
dir = dirs[(dirs.index(dir) + 1) % len(dirs)]
inf.remove(pos)
else:
# turn left
dir = dirs[(dirs.index(dir) - 1) % len(dirs)]
inf.add(pos)
burst_inf += 1
pos = add2(pos, dir)
print(burst_inf)
def part_2(data):
steps = 10000000
g = Grid2D(data)
dirs = [g.N, g.E, g.S, g.W]
pos = (g.n_rows // 2, g.n_cols // 2)
dir = g.N
weak = set()
inf = set(g.find("#"))
flagged = set()
burst_inf = 0
for _ in range(steps):
if pos in weak:
weak.remove(pos)
inf.add(pos)
burst_inf += 1
elif pos in inf:
dir = dirs[(dirs.index(dir) + 1) % len(dirs)]
inf.remove(pos)
flagged.add(pos)
elif pos in flagged:
dir = dirs[(dirs.index(dir) + 2) % len(dirs)]
flagged.remove(pos)
else:
dir = dirs[(dirs.index(dir) - 1) % len(dirs)]
weak.add(pos)
pos = add2(pos, dir)
print(burst_inf)
def main():
data = open(0).read()
part_1(data)
part_2(data)
if __name__ == "__main__":
main()

108
2017/d23.py Normal file
View File

@@ -0,0 +1,108 @@
import subprocess
from collections import defaultdict
def print_game(regs, i, insts):
print("\033[H\033[J", end="")
for j, inst in enumerate(insts):
start = " "
if j == i:
start = "> "
print(f"{start} {' '.join(inst)}")
for c in "abcdefgh":
print(c, regs[c])
def part_1(data):
regs = {c: 0 for c in "abcdefgh"}
insts = list(map(lambda line: line.split(), data.splitlines()))
i = 0
while i < len(insts):
inst = insts[i]
match inst:
case ["set", reg, val]:
regs[reg] = regs[val] if val in regs else int(val)
case ["sub", reg, val]:
regs[reg] -= regs[val] if val in regs else int(val)
case ["mul", reg, val]:
regs[reg] *= regs[val] if val in regs else int(val)
case ["jnz", reg, val]:
val = regs[val] if val in regs else int(val)
assert type(val) is int
if val != 0:
i += (val - 1)
case _:
assert False
i += 1
def to_c(data):
insts = list(map(lambda line: line.split(), data.splitlines()))
label_count = 0
labels = defaultdict(list)
for i, inst in enumerate(insts):
match inst:
case ["jnz", reg, val]:
label = f"label_{label_count}"
label_count += 1
inst[2] = label
label_i = i + int(val)
labels[label_i].append(label)
case _:
pass
i = 0
begin = ["#include <stdio.h>", "",
"int mul(int a, int b) {",
" return a * b;",
"}",
"",
"int main() {" ]
decls = " int "
for v in "abcdefgh":
decls += f"{v} = 0, "
decls = decls[:-2] + ";"
begin.append(decls)
body = []
end = [ " return 0;", "}", ]
for i, inst in enumerate(insts):
if i in labels:
for label in labels[i]:
body.append(label + ":")
match inst:
case ["set", reg, val]:
body.append(f" {reg} = {val};")
case ["sub", reg, val]:
body.append(f" {reg} -= {val};")
case ["mul", reg, val]:
body.append(f" {reg} = mul({reg}, {val});")
case ["jnz", reg, val]:
body.append(f" if ({reg} != 0) goto {val};")
case _:
assert False
body.append(labels[i + 1][0] + ":")
all = begin + body + end
with open("i23.c", "w") as f:
for line in all:
f.write(line + "\n")
def run_c():
subprocess.call(["gcc", "i23.c"])
subprocess.call(["./a.out"])
subprocess.call(["rm", "a.out"])
def main():
with open("i23.txt") as f:
data = f.read()
# to_c(data)
# part_1(data)
run_c()
if __name__ == "__main__":
main()

35
2017/d24.py Normal file
View File

@@ -0,0 +1,35 @@
from collections import defaultdict
def part_1(data):
ports = [list(map(int, line.split("/"))) for line in data.splitlines()]
in_to_out = defaultdict(list)
for index, (i, o) in enumerate(ports):
in_to_out[i].append((o, index))
in_to_out[o].append((i, index))
max_sum = 0
max_len = (0, 0)
bs = [(0, [], 0, 0)]
while bs:
current_port, used, current_sum, current_len = bs.pop()
max_sum = max(max_sum, current_sum)
max_len = max(max_len, (current_len, current_sum))
for out_port, index in in_to_out[current_port]:
if index in used:
continue
new_sum = current_sum + current_port + out_port
bs.append((out_port, used + [index], new_sum, current_len + 1))
print(max_sum)
print(max_len[1])
def main():
with open("i24.txt") as f:
data = f.read()
part_1(data)
if __name__ == "__main__":
main()

58
2017/d25.py Normal file
View File

@@ -0,0 +1,58 @@
from lib import str_to_int
from collections import defaultdict
def part_1(data):
rules = {}
steps = None
start_state = None
current_state = None
current_value = None
for line in data.splitlines():
if line.startswith("In state"):
current_state = line[-2]
rules[current_state] = {}
elif "current value" in line:
current_value = str_to_int(line)
rules[current_state][current_value] = []
elif "- Write the value" in line:
rules[current_state][current_value].append(str_to_int(line))
elif "- Move one slot to the right" in line:
rules[current_state][current_value].append(1)
elif "- Move one slot to the left" in line:
rules[current_state][current_value].append(-1)
elif "Continue with state" in line:
rules[current_state][current_value].append(line[-2])
elif "Begin in state" in line:
start_state = line[-2]
elif "checksum after" in line:
steps = str_to_int(line)
elif line.strip() == "":
pass
else:
print(line)
assert False
assert type(steps) is int
assert start_state is not None
current_state = start_state
tape = defaultdict(int)
pos = 0
for _ in range(steps):
current_value = tape[pos]
write_value, move, next_state = rules[current_state][current_value]
tape[pos] = write_value
pos += move
current_state = next_state
print(sum(tape.values()))
def main():
with open("i25.txt") as f:
data = f.read()
part_1(data)
if __name__ == "__main__":
main()

16
2017/d4.py Normal file
View File

@@ -0,0 +1,16 @@
data = open(0).read().strip()
part_2 = False
r = 0
words = []
for line in data.splitlines():
if part_2:
words = list(map(lambda w: "".join(sorted(w)), line.split()))
else:
words = line.split()
if len(words) == len(set(words)):
r += 1
print(r)

17
2017/d5.py Normal file
View File

@@ -0,0 +1,17 @@
insts = list(map(int, open(0).read().strip().splitlines()))
part_2 = True
c = 0
i = 0
while i < len(insts):
ci = i
i += insts[i]
if part_2:
if insts[ci] >= 3:
insts[ci] -= 1
else:
insts[ci] += 1
else:
insts[ci] += 1
c += 1
print(c)

27
2017/d6.py Normal file
View File

@@ -0,0 +1,27 @@
data = open(0).read().strip()
# data = "0 2 7 0"
banks = list(map(int, data.split()))
seen = dict()
seen[tuple(banks)] = 0
c = 0
while True:
c += 1
max_index = banks.index(max(banks))
blocks = banks[max_index]
banks[max_index] = 0
index = max_index
while blocks > 0:
index = (index + 1) % len(banks)
if index == max_index:
continue
banks[index] += 1
blocks -= 1
if tuple(banks) in seen:
break
seen[tuple(banks)] = c
print(c)
print(c - seen[tuple(banks)])

63
2017/d7.py Normal file
View File

@@ -0,0 +1,63 @@
from dataclasses import dataclass
from collections import defaultdict
import sys
@dataclass
class Disc:
name: str
value: int
holds: list
discs = {}
data = open(0).read().strip()
for line in data.splitlines():
right = None
if "->" in line:
left, right = line.split(" -> ")
else:
left = line
name, value = left.split()
value = int(value[1:-1])
d = Disc(name, value, [])
if right is not None:
for r in right.split(", "):
d.holds.append(r)
discs[d.name] = d
alldiscs = set(discs.keys())
for d in discs.values():
for h in d.holds:
alldiscs.remove(h)
assert len(alldiscs) == 1
top_disc = alldiscs.pop()
print(top_disc)
def weight(disc_name):
disc = discs[disc_name]
child_weights = {}
for child_disc_name in disc.holds:
child_weights[child_disc_name] = weight(child_disc_name)
if not all([w == list(child_weights.values())[0] for w in child_weights.values()]):
values = defaultdict(list)
for child_name, child_value in child_weights.items():
values[child_value].append(child_name)
same_weight, other_weight = 0, 0
for child_weight, children in values.items():
if len(children) == 1:
other_weight = child_weight
else:
same_weight = child_weight
weight_delta = other_weight - same_weight
print(discs['dqwocyn'].value - weight_delta)
sys.exit(0)
return disc.value + sum(child_weights.values())
weight(top_disc)

28
2017/d8.py Normal file
View File

@@ -0,0 +1,28 @@
from collections import defaultdict
def part_1(data):
regs = defaultdict(int)
allmax = 0
for line in data.splitlines():
match line.split():
case [reg_a, op, value, "if", reg_c, cmp, val_c]:
value, val_c = int(value), int(val_c)
reg_val = regs[reg_c]
if eval(f"{reg_val} {cmp} {val_c}"):
value = value if op == "inc" else -value
regs[reg_a] += value
case _:
assert False
allmax = max(max(regs.values()), allmax)
print(max(regs.values()))
print(allmax)
def main():
data = open(0).read().strip()
part_1(data)
if __name__ == "__main__":
main()

33
2017/d9.py Normal file
View File

@@ -0,0 +1,33 @@
def part_1(data):
i = 0
group_score, garbage_score = 0, 0
in_garbage = False
group_level = 0
while i < len(data):
match data[i]:
case "{" if not in_garbage:
group_level += 1
case "}" if not in_garbage:
assert group_level > 0
group_score += group_level
group_level -= 1
case "<" if not in_garbage:
in_garbage = True
case ">" if in_garbage:
in_garbage = False
case "!" if in_garbage:
i += 1
case _ if in_garbage:
garbage_score += 1
i += 1
print(group_score)
print(garbage_score)
def main():
data = open(0).read().strip()
part_1(data)
if __name__ == "__main__":
main()

65
2017/i23.c Normal file
View File

@@ -0,0 +1,65 @@
#include <stdio.h>
unsigned int mul_count = 0;
int mul(int a, int b) {
mul_count++;
return a * b;
}
int main() {
for (int a = 0; a < 2; a++) {
int b = 0, c = 0, d = 0, e = 0, f = 0, g = 1, h = 0;
b = 67;
c = b;
if (a != 0) {
b = mul(b, 100);
b += 100000;
c = b;
c += 17000;
}
while (1) {
f = 1;
d = 2;
while (g != 0)
{
e = 2;
while (g != 0) {
if (b % d == 0) {
int e_required = b / d;
if (e_required >= 2 && e_required <= b) {
f = 0;
}
}
mul_count += (b - e);
g = 0;
}
d += 1;
g = d;
g -= b;
}
if (f == 0) {
h += 1;
}
g = b;
g -= c;
if (g != 0) {
b += 17;
} else {
break;
}
}
if (a == 0) {
printf("%d\n", mul_count);
} else {
printf("%d\n", h);
}
}
return 0;
}

View File

@@ -27,6 +27,9 @@ def mape(f, xs):
def add2(a: tuple[int, int], b: tuple[int, int]) -> tuple[int, int]: def add2(a: tuple[int, int], b: tuple[int, int]) -> tuple[int, int]:
return (a[0] + b[0], a[1] + b[1]) return (a[0] + b[0], a[1] + b[1])
def sub2(a: tuple[int, int], b: tuple[int, int]) -> tuple[int, int]:
return (a[0] - b[0], a[1] - b[1])
class Grid2D: class Grid2D:
N = (-1, 0) N = (-1, 0)
E = (0, 1) E = (0, 1)

28
2018/d1.py Normal file
View File

@@ -0,0 +1,28 @@
def part_1(data):
r = 0
for x in data.splitlines():
r += int(x)
print(r)
def part_2(data):
seen = set()
r = 0
while True:
for x in data.splitlines():
r += int(x)
if r in seen:
print(r)
return
seen.add(r)
def main():
with open("i1.txt") as f:
data = f.read()
part_1(data)
part_2(data)
if __name__ == "__main__":
main()

54
2018/d10.py Normal file
View File

@@ -0,0 +1,54 @@
from lib import str_to_ints
def print_points(objs):
x_min, x_max = 10**9, 0
y_min, y_max = 10**9, 0
exist = set()
for x, y, _, _ in objs:
x_min = min(x_min, x)
y_min = min(y_min, y)
x_max = max(x_max, x)
y_max = max(y_max, y)
exist.add((x, y))
if x_max - x_min > 67:
return False
for y in range(y_min, y_max + 1):
s = ""
for x in range(x_min, x_max + 1):
if (x, y) in exist:
s += "X"
else:
s += " "
print(s)
return True
def part_1(data):
objs = []
for line in data.splitlines():
x, y, dx, dy = str_to_ints(line)
objs.append((x,y, dx, dy))
for t in range(1_000_000):
for i in range(len(objs)):
x, y, dx, dy = objs[i]
on = (x + dx, y + dy, dx, dy)
objs[i] = on
if print_points(objs):
print(t + 1)
break
def main():
input_file = __file__.replace(".py", ".txt")
with open(input_file) as f:
data = f.read()
part_1(data)
if __name__ == "__main__":
main()

80
2018/d11.py Normal file
View File

@@ -0,0 +1,80 @@
from functools import lru_cache
@lru_cache
def score(serial, x, y):
# Find the fuel cell's rack ID, which is its X coordinate plus 10.
# Begin with a power level of the rack ID times the Y coordinate.
# Increase the power level by the value of the grid serial number (your puzzle input).
# Set the power level to itself multiplied by the rack ID.
# Keep only the hundreds digit of the power level (so 12345 becomes 3; numbers with no hundreds digit become 0).
# Subtract 5 from the power level.
rack_id = x + 10
power_level = rack_id * y
power_level += serial
power_level *= rack_id
try:
return int(str(power_level)[-3]) - 5
except IndexError:
return -5
def part_1(data):
v = int(data.strip())
max_val = 0
max_coord = None
assert score(8, 3, 5) == 4
assert score(57, 122, 79) == -5
assert score(39, 217, 196) == 0
for x in range(1, 299):
for y in range(1, 299):
s = 0
for x2 in range(x, x + 3):
for y2 in range(y, y + 3):
s += score(v, x2, y2)
if x2 > 300 or y2 > 300:
assert False
if s > max_val:
max_val = s
max_coord = (x, y)
assert max_coord is not None
print(",".join(list(map(str, max_coord))))
def part_2(data):
v = int(data.strip())
max_val = 0
max_coord = None
# brute force, just tried if 15 is small enough
for size in range(2, 15):
for x in range(1, 299):
for y in range(1, 299):
s = 0
if x + size > 300 or y + size > 300:
break
for x2 in range(x, x + size):
for y2 in range(y, y + size):
s += score(v, x2, y2)
if x2 > 300 or y2 > 300:
assert False
if s > max_val:
max_val = s
max_coord = (x, y, size)
assert max_coord is not None
print(",".join(list(map(str, max_coord))))
def main():
input_file = __file__.replace(".py", ".txt")
with open(input_file) as f:
data = f.read()
part_1(data)
part_2(data)
if __name__ == "__main__":
main()

60
2018/d12.py Normal file
View File

@@ -0,0 +1,60 @@
def score(state, offset):
r = 0
for i in range(len(state)):
if state[i] == "#":
r += i + offset
return r
def solve(data, target_i):
state = None
map = {}
for line in data.splitlines():
if "initial state" in line:
_, rest = line.split(":")
state = rest.strip()
if "=>" in line:
lhs, rhs = line.split(" => ")
assert lhs not in map
map[lhs] = rhs
assert state is not None
seen = {}
offset = 0
for i in range(target_i):
while not state.startswith("...."):
state = "." + state
offset -= 1
while not state.endswith("...."):
state += "."
if state in seen:
prev_i, prev_score = seen[state]
assert (i - prev_i) == 1
current_score = score(state, offset)
delta_score = current_score - prev_score
final_score = current_score + (target_i - i) * delta_score
print(final_score)
return
else:
seen[state] = (i, score(state, offset))
ns = ""
for i in range(2, len(state) - 2):
s = state[i - 2 : i + 3]
ns += map[s]
state = ns
offset += 2
print(score(state, offset))
def main():
input_file = __file__.replace(".py", ".txt")
with open(input_file) as f:
data = f.read()
solve(data, 20)
solve(data, 50 * 10**9)
if __name__ == "__main__":
main()

102
2018/d13.py Normal file
View File

@@ -0,0 +1,102 @@
from lib import Grid2D, add2
CARTS = (
("^", Grid2D.N, "|"),
("v", Grid2D.S, "|"),
(">", Grid2D.E, "-"),
("<", Grid2D.W, "-"),
)
TURNS = "lsr"
DIRS = [(-1, 0), (0, 1), (1, 0), (0, -1)]
def part_1(data):
g = Grid2D(data)
first_shown = False
carts = []
occupied = set()
for c, d, r in CARTS:
for cart in g.find(c):
carts.append((cart, d, "l"))
g[cart] = r
occupied.add(cart)
for _ in range(1, 100_000):
carts = sorted(carts)
ncarts = []
i_to_skip = []
for i in range(len(carts)):
if i in i_to_skip:
continue
pos, dir, turn = carts[i]
occupied.remove(pos)
pos = add2(pos, dir)
# detect collision and remove carts
if pos in occupied:
if first_shown is False:
print(f"{pos[1]},{pos[0]}")
first_shown = True
# either from carts that haven't moved, yet
for j in range(len(carts)):
jpos, _, _ = carts[j]
if jpos == pos:
i_to_skip.append(j)
break
else:
# or from carts that have already moved
ncarts = list(filter(lambda c: c[0] != pos, ncarts))
occupied.remove(pos)
continue
else:
occupied.add(pos)
if g[pos] == "+":
if turn == "l":
dir = DIRS[(DIRS.index(dir) - 1) % len(DIRS)]
elif turn == "r":
dir = DIRS[(DIRS.index(dir) + 1) % len(DIRS)]
turn = TURNS[(TURNS.index(turn) + 1) % len(TURNS)]
elif g[pos] == "/":
if dir == g.E:
dir = g.N
elif dir == g.W:
dir = g.S
elif dir == g.N:
dir = g.E
elif dir == g.S:
dir = g.W
else:
assert False
elif g[pos] == "\\":
if dir == g.E:
dir = g.S
elif dir == g.W:
dir = g.N
elif dir == g.N:
dir = g.W
elif dir == g.S:
dir = g.E
else:
assert False
ncarts.append((pos, dir, turn))
carts = ncarts
if len(carts) == 1:
pos = carts[0][0]
print(f"{pos[1]},{pos[0]}")
return
def main():
input_file = __file__.replace(".py", ".txt")
with open(input_file) as f:
data = f.read()
part_1(data)
if __name__ == "__main__":
main()

62
2018/d14.py Normal file
View File

@@ -0,0 +1,62 @@
def part_1(data):
num = int(data.strip())
recipes = [3, 7]
i, j = 0, 1
while True:
s = recipes[i] + recipes[j]
ss = str(s)
if len(ss) == 1:
recipes.append(s)
elif len(ss) == 2:
recipes.append(int(ss[0]))
recipes.append(int(ss[1]))
else:
assert False
i = (i + 1 + recipes[i]) % len(recipes)
j = (j + 1 + recipes[j]) % len(recipes)
if i == j:
j = (j + 1) % len(recipes)
if len(recipes) > num + 10:
print("".join(map(str, recipes[num : num + 10])))
break
def part_2(data):
target = list(map(int, data.strip()))
recipes = [3, 7]
i, j = 0, 1
while True:
s = recipes[i] + recipes[j]
if s < 10:
recipes.append(s)
elif s < 100:
recipes.append(s // 10)
recipes.append(s % 10)
else:
assert False
i = (i + 1 + recipes[i]) % len(recipes)
j = (j + 1 + recipes[j]) % len(recipes)
if i == j:
j = (j + 1) % len(recipes)
if recipes[-len(target) :] == target:
print(len(recipes) - len(target))
elif recipes[-len(target) - 1 : -1] == target:
print(len(recipes) - len(target) - 1)
break
def main():
input_file = __file__.replace(".py", ".txt")
with open(input_file) as f:
data = f.read()
part_1(data)
part_2(data)
if __name__ == "__main__":
main()

117
2018/d15.py Normal file
View File

@@ -0,0 +1,117 @@
from lib import Grid2D, add2
from collections import deque
DIRS = [Grid2D.N, Grid2D.W, Grid2D.E, Grid2D.S]
def find_move(unit, grid):
assert grid[unit] in "GE"
target_fields = set()
enemies = grid.find("G") if grid[unit] == "E" else grid.find("E")
for e in enemies:
for dir in DIRS:
nb = add2(e, dir)
if grid[nb] == ".":
target_fields.add(nb)
# we are already next to an enemy
if unit in target_fields:
return None
seen = set()
states = deque([(unit, [])])
while states:
pos, hist = states.popleft()
if pos in seen:
continue
else:
seen.add(pos)
for dir in DIRS:
nb = add2(pos, dir)
if nb in target_fields:
if hist:
return hist[0]
else:
return nb
elif grid[nb] == ".":
nhist = hist + [nb]
states.append((nb, nhist))
# we did not find a path to an enemy
return None
def part_2(data):
for elf_attack in range(3, 100):
g = Grid2D(data)
pos_to_health = {pos: 200 for pos in g.find("GE")}
len_elfs_orig = len(g.find("E"))
rounds = 0
done = False
while not done:
for unit in sorted(list(pos_to_health.keys())):
if not unit in pos_to_health:
continue # unit died in the meantime
enemy_type = "G" if g[unit] == "E" else "E"
# move stage
for dir in DIRS:
nb = add2(unit, dir)
if g[nb] == enemy_type:
break # we are already next to an enemy
else:
npos = find_move(unit, g)
if npos is not None:
assert npos not in pos_to_health and g[npos] == "."
pos_to_health[npos] = pos_to_health[unit]
del pos_to_health[unit]
g[npos] = g[unit]
g[unit] = "."
unit = npos
# attack stage
enemy_type = "G" if g[unit] == "E" else "E"
enemy_hit = 999
enemy = None
for dir in DIRS:
nb = add2(unit, dir)
if g[nb] == enemy_type and pos_to_health[nb] < enemy_hit:
enemy_hit = pos_to_health[nb]
enemy = nb
if enemy is not None:
if g[enemy] == "G":
pos_to_health[enemy] -= elf_attack
else:
pos_to_health[enemy] -= 3
if pos_to_health[enemy] <= 0:
del pos_to_health[enemy]
g[enemy] = "."
if len(g.find("E")) == 0 or len(g.find("G")) == 0:
done = True
break
else:
rounds += 1
if elf_attack == 3:
print(sum(pos_to_health.values()) * rounds)
if len(g.find("E")) == len_elfs_orig:
print(sum(pos_to_health.values()) * (rounds))
break
def main():
input_file = __file__.replace(".py", ".txt")
with open(input_file) as f:
data = f.read()
part_2(data)
if __name__ == "__main__":
main()

147
2018/d16.py Normal file
View File

@@ -0,0 +1,147 @@
from lib import str_to_ints, get_data
def addr(regs, a, b, c):
regs[c] = regs[a] + regs[b]
def addi(regs, a, b, c):
regs[c] = regs[a] + b
def mulr(regs, a, b, c):
regs[c] = regs[a] * regs[b]
def muli(regs, a, b, c):
regs[c] = regs[a] * b
def banr(regs, a, b, c):
regs[c] = regs[a] & regs[b]
def bani(regs, a, b, c):
regs[c] = regs[a] & b
def borr(regs, a, b, c):
regs[c] = regs[a] | regs[b]
def bori(regs, a, b, c):
regs[c] = regs[a] | b
def setr(regs, a, _, c):
regs[c] = regs[a]
def seti(regs, a, _, c):
regs[c] = a
def gtir(regs, a, b, c):
regs[c] = 1 if a > regs[b] else 0
def gtri(regs, a, b, c):
regs[c] = 1 if regs[a] > b else 0
def gtrr(regs, a, b, c):
regs[c] = 1 if regs[a] > regs[b] else 0
def eqir(regs, a, b, c):
regs[c] = 1 if a == regs[b] else 0
def eqri(regs, a, b, c):
regs[c] = 1 if regs[a] == b else 0
def eqrr(regs, a, b, c):
regs[c] = 1 if regs[a] == regs[b] else 0
OPS = [
addr,
addi,
mulr,
muli,
banr,
bani,
borr,
bori,
setr,
seti,
gtir,
gtri,
gtrr,
eqir,
eqri,
eqrr,
]
def part_1(data):
examples = []
lines = data.splitlines()
test_prog = []
last_i = None
for i in range(len(lines)):
line = lines[i]
if line.startswith("Before:"):
regs = str_to_ints(line)
inst = str_to_ints(lines[i + 1])
regs_after = str_to_ints(lines[i + 2])
examples.append((regs, inst, regs_after))
last_i = i + 2
assert last_i is not None
for line in lines[last_i:]:
if line.strip() != "":
test_prog.append(str_to_ints(line))
r = 0
for before_orig, inst, after in examples:
ops_correct = []
for op in OPS:
before = list(before_orig)
op(before, *inst[1:])
if before == after:
ops_correct.append(op)
if len(ops_correct) >= 3:
r += 1
print(r)
code_to_op = {}
while len(OPS) > 0:
for before_orig, inst, after in examples:
ops_correct = []
for op in OPS:
before = list(before_orig)
op(before, *inst[1:])
if before == after:
ops_correct.append(op)
if len(ops_correct) == 1:
code_to_op[inst[0]] = ops_correct[0]
OPS.remove(ops_correct[0])
regs = [0, 0, 0, 0]
for line in test_prog:
op_code = line[0]
vals = line[1:]
code_to_op[op_code](regs, *vals)
print(regs[0])
def main():
data = get_data(__file__)
part_1(data)
if __name__ == "__main__":
main()

136
2018/d17.py Normal file
View File

@@ -0,0 +1,136 @@
from lib import get_data, str_to_ints, add2
def above(p):
return add2(p, (0, -1))
def below(p):
return add2(p, (0, 1))
def right(p):
return add2(p, (1, 0))
def left(p):
return add2(p, (-1, 0))
def print2d(xs, ys=[], zs=[], cs=".#~|"):
xs, ys, zs = set(xs), set(ys), set(zs)
all = xs | ys | zs
x_min, x_max, y_min, y_max = 10**9, 0, 10**9, 0
for x, y in all:
x_min = min(x_min, x)
y_min = min(y_min, y)
x_max = max(x_max, x)
y_max = max(y_max, y)
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 += cs[1]
elif (x, y) in ys:
row += cs[2]
elif (x, y) in zs:
row += cs[3]
else:
row += cs[0]
print(row)
def part_1(data):
sand = []
max_y = 0
min_y = 10**9
for line in data.splitlines():
line = line.strip()
a, b, c = str_to_ints(line)
if line.startswith("x"):
x = a
for y in range(b, c + 1):
max_y = max(max_y, y)
min_y = min(min_y, y)
sand.append((x, y))
elif line.startswith("y"):
y = a
max_y = max(max_y, y)
min_y = min(min_y, y)
for x in range(b, c + 1):
sand.append((x, y))
else:
assert False
# TODO: probably should use 2D array for better perf
sources = [(500, 0)]
water = set()
sand = set(sand)
water_rest = set()
while sources:
source = sources.pop()
current = below(source)
while current[1] <= max_y:
if current[1] < min_y:
current = below(current)
elif current in water_rest:
break
elif below(current) in sand or below(current) in water_rest:
water.add(current)
c = current
to_water_rest = [current]
left_sand, right_sand = False, False
while True:
c = right(c)
if c in sand:
right_sand = True
break
elif below(c) in sand or below(c) in water_rest:
water.add(c)
to_water_rest.append(c)
else:
# it's empty underneath
water.add(c)
sources.append(c)
break
c = current
while True:
c = left(c)
if c in sand:
left_sand = True
break
elif below(c) in sand or below(c) in water_rest:
water.add(c)
to_water_rest.append(c)
else:
# it's empty underneath
water.add(c)
sources.append(c)
break
if left_sand and right_sand:
sources.append(above(above(current)))
for w in to_water_rest:
water_rest.add(w)
break
else:
water.add(current)
current = below(current)
# print2d(sand, water_rest, water)
# print(sources)
# input()
current_water = len(water_rest | water)
print(current_water)
print(len(water_rest))
def main():
data = get_data(__file__)
part_1(data)
if __name__ == "__main__":
main()

68
2018/d18.py Normal file
View File

@@ -0,0 +1,68 @@
from lib import get_data, Grid2D
from copy import deepcopy
def part_2(data, target=10):
g = Grid2D(data)
seen = {}
i = 0
while i < target:
ng = deepcopy(g)
for r in range(g.n_rows):
for c in range(g.n_cols):
if g[(r, c)] == ".":
tree_count = sum(
[1 if g[nb] == "|" else 0 for nb in g.neighbors_adj((r, c))]
)
if tree_count >= 3:
ng[(r, c)] = "|"
elif g[(r, c)] == "|":
lumber_count = sum(
[1 if g[nb] == "#" else 0 for nb in g.neighbors_adj((r, c))]
)
if lumber_count >= 3:
ng[(r, c)] = "#"
elif g[(r, c)] == "#":
tree_count = sum(
[1 if g[nb] == "|" else 0 for nb in g.neighbors_adj((r, c))]
)
lumber_count = sum(
[1 if g[nb] == "#" else 0 for nb in g.neighbors_adj((r, c))]
)
if tree_count > 0 and lumber_count > 0:
pass
else:
ng[(r, c)] = "."
else:
assert False
h = ng.hash()
if h in seen:
delta = i - seen[h]
while (i + delta) < target:
i += delta
seen = {}
else:
seen[h] = i
i += 1
g = ng
w = 0
l = 0
for r in range(g.n_rows):
for c in range(g.n_cols):
if g[(r, c)] == "|":
w += 1
elif g[(r, c)] == "#":
l += 1
print(l * w)
def main():
data = get_data(__file__)
part_2(data, 10)
part_2(data, 1000000000)
if __name__ == "__main__":
main()

134
2018/d19.py Normal file
View File

@@ -0,0 +1,134 @@
from lib import get_data, str_to_ints
import d16
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] = 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
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])
f(regs, *inst[1:])
regs[ip] += 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]
"""

53
2018/d2.py Normal file
View File

@@ -0,0 +1,53 @@
from lib import LETTERS_LOWER
def part_1(data):
two_count, three_count = 0, 0
for line in data.splitlines():
count = [line.count(c) for c in LETTERS_LOWER if line.count(c) > 1]
if 2 in count:
two_count += 1
if 3 in count:
three_count += 1
r = two_count * three_count
print(r)
def equal(a, b):
c = 0
if len(a) != len(b):
return False
for i in range(len(a)):
if a[i] != b[i]:
c += 1
if c == 1:
return True
return False
def strip(a, b):
r = ""
for a, b in zip(a, b):
if a == b:
r += a
return r
def part_2(data):
lines = list(data.splitlines())
for i in range(len(lines)):
for j in range(i + 1, len(lines)):
a, b = lines[i], lines[j]
if equal(a, b):
print(strip(a, b))
def main():
with open("i2.txt") as f:
data = f.read()
part_1(data)
part_2(data)
if __name__ == "__main__":
main()

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

44
2018/d3.py Normal file
View File

@@ -0,0 +1,44 @@
from lib import str_to_ints
from collections import defaultdict
def part_1(data):
claims = defaultdict(int)
for line in data.splitlines():
id, x, y, w, h = str_to_ints(line)
for dx in range(w):
for dy in range(h):
claims[(x + dx, y + dy)] += 1
r = sum([1 for v in claims.values() if v > 1])
print(r)
def part_2(data):
claims = defaultdict(list)
all_ids = set()
for line in data.splitlines():
id, x, y, w, h = str_to_ints(line)
for dx in range(w):
for dy in range(h):
claims[(x + dx, y + dy)].append(id)
all_ids.add(id)
for xs in claims.values():
if len(xs) > 1:
for x in xs:
if x in all_ids:
all_ids.remove(x)
assert len(all_ids) == 1
print(all_ids.pop())
def main():
with open("i3.txt") as f:
data = f.read()
part_1(data)
part_2(data)
if __name__ == "__main__":
main()

67
2018/d4.py Normal file
View File

@@ -0,0 +1,67 @@
from lib import str_to_ints
from collections import defaultdict
def part_1(data):
shifts = sorted(data.splitlines())
guards = defaultdict(list)
guard = None
for s in shifts:
try:
_, _, _, h, m, guard = str_to_ints(s)
except ValueError:
_, _, _, h, m = str_to_ints(s)
assert guard is not None
guards[guard].append((h, m))
guard_sleep_times = {}
guard_heat_maps = {}
for guard, times in guards.items():
heat_map = [0 for _ in range(60)]
total_time = 0
start = None
for h, m in times:
if start is None:
start = (h, m)
else:
delta = (h - start[0]) * 60 + (m - start[1])
total_time += delta
for t in range(start[1], m):
heat_map[t] += 1
start = None
guard_sleep_times[guard] = total_time
guard_heat_maps[guard] = heat_map
max_guard, max_time = None, 0
for guard, time in guard_sleep_times.items():
if time > max_time:
max_guard = guard
max_time = time
assert type(max_guard) is int
max_minute, max_asleep = 0, 0
for minute, asleep in enumerate(guard_heat_maps[max_guard]):
if asleep > max_asleep:
max_minute = minute
max_asleep = asleep
print(max_guard * max_minute)
max_guard, max_minute, max_asleep = 0, 0, 0
for guard in guard_sleep_times.keys():
for minute, asleep in enumerate(guard_heat_maps[guard]):
if asleep > max_asleep:
max_minute = minute
max_asleep = asleep
max_guard = guard
print(max_guard * max_minute)
def main():
input_file = __file__.replace(".py", ".txt")
with open(input_file) as f:
data = f.read()
part_1(data)
if __name__ == "__main__":
main()

46
2018/d5.py Normal file
View File

@@ -0,0 +1,46 @@
from lib import LETTERS_LOWER
def part_1(data):
data = data.strip()
i = 0
while i < (len(data) - 1):
a, b = data[i], data[i + 1]
if (a.upper() == b.upper()) and (a != b):
data = data[:i] + data[i + 2 :]
i = max(0, i - 1)
else:
i += 1
print(len(data))
def part_2(data):
data_orig = data.strip()
min_len = float("inf")
for c in LETTERS_LOWER:
data = str(data_orig)
data = data.replace(c, "")
data = data.replace(c.upper(), "")
i = 0
while i < (len(data) - 1):
a, b = data[i], data[i + 1]
if (a.upper() == b.upper()) and (a != b):
data = data[:i] + data[i + 2 :]
i = max(0, i - 1)
else:
i += 1
min_len = min(min_len, len(data))
print(min_len)
def main():
input_file = __file__.replace(".py", ".txt")
with open(input_file) as f:
data = f.read()
part_1(data)
part_2(data)
if __name__ == "__main__":
main()

66
2018/d6.py Normal file
View File

@@ -0,0 +1,66 @@
from lib import str_to_ints
def part_1(data):
counts = {}
x_min, x_max, y_min, y_max = 10**9, 0, 10**9, 0
for line in data.splitlines():
x, y = str_to_ints(line)
x_min = min(x_min, x)
x_max = max(x_max, x)
y_min = min(y_min, y)
y_max = max(y_max, y)
counts[(x, y)] = 0
infs = set()
for x in range(x_min, x_max + 1):
for y in range(y_min, y_max + 1):
if (x, y) in counts:
counts[(x, y)] += 1
continue
min_dist = 10**9
min_fields = []
for (nx, ny) in counts.keys():
d = abs(nx - x) + abs(ny - y)
if d == min_dist:
min_fields.append((nx, ny))
elif d < min_dist:
min_dist = d
min_fields = [(nx, ny)]
if len(min_fields) == 1:
(nx, ny), = min_fields
if x == x_min or y == y_min or x == x_max or y == y_max:
infs.add((nx, ny))
else:
counts[(nx, ny)] += 1
for c in infs:
del counts[c]
print(max(list(counts.values())))
def part_2(data):
coords = []
for line in data.splitlines():
coords.append(str_to_ints(line))
r = 0
for x in range(-500, 500):
for y in range(-500, 500):
d = sum([abs(nx - x) + abs(ny - y) for (nx, ny) in coords])
if d < 10_000:
r += 1
print(r)
def main():
input_file = __file__.replace(".py", ".txt")
with open(input_file) as f:
data = f.read()
part_1(data)
part_2(data)
if __name__ == "__main__":
main()

80
2018/d7.py Normal file
View File

@@ -0,0 +1,80 @@
from lib import LETTERS_UPPER
def part_1(data):
deps = {c: [] for c in LETTERS_UPPER}
for line in data.splitlines():
fields = line.split()
a, b = fields[1], fields[7]
deps[b].append(a)
ready = []
for x, ys in deps.items():
if len(ys) == 0:
ready.append(x)
seq = []
ready = sorted(ready)
while ready:
current, ready = ready[0], ready[1:]
seq.append(current)
for x, ys in deps.items():
if current in ys:
ys.remove(current)
if len(ys) == 0:
ready.append(x)
ready = sorted(ready)
print("".join(seq))
def score(c):
return 1 + ord(c) - ord("A") + 60
def part_2(data):
deps = {c: [] for c in LETTERS_UPPER}
for line in data.splitlines():
fields = line.split()
a, b = fields[1], fields[7]
deps[b].append(a)
ready = []
done = []
for x, ys in deps.items():
if len(ys) == 0:
ready.append(x)
workers = {}
steps = 0
while len(done) != 26:
ready = sorted(ready)
while len(ready) > 0 and len(workers) < 5:
current, ready = ready[0], ready[1:]
workers[current] = score(current)
to_del = []
for k in workers.keys():
workers[k] -= 1
if workers[k] == 0:
to_del.append(k)
done.append(k)
for x, ys in deps.items():
if k in ys:
ys.remove(k)
if len(ys) == 0:
ready.append(x)
for k in to_del:
del workers[k]
steps += 1
print(steps)
def main():
input_file = __file__.replace(".py", ".txt")
with open(input_file) as f:
data = f.read()
part_1(data)
part_2(data)
if __name__ == "__main__":
main()

59
2018/d8.py Normal file
View File

@@ -0,0 +1,59 @@
from lib import *
def parse(fields, i):
n_children = fields[i]
n_meta = fields[i + 1]
i += 2
children = []
meta_score = 0
for _ in range(n_children):
c, i, m = parse(fields, i)
children.append(c)
meta_score += m
for i in range(i, i + n_meta):
meta_score += fields[i]
i += 1
return children, i, meta_score
def parse2(fields, i):
n_children = fields[i]
n_meta = fields[i + 1]
i += 2
children = []
scores = []
for _ in range(n_children):
c, i, s = parse2(fields, i)
children.append(c)
scores.append(s)
score = 0
if n_children == 0:
for j in range(i, i + n_meta):
score += fields[j]
else:
for j in range(i, i + n_meta):
new_i = fields[j]
new_i -= 1
if new_i < 0:
continue
if new_i < len(children):
score += scores[new_i]
i += n_meta
return children, i, score
def main():
input_file = __file__.replace(".py", ".txt")
with open(input_file) as f:
data = f.read()
nums = str_to_ints(data)
tree = parse(nums, 0)
print(tree[2])
tree = parse2(nums, 0)
print(tree[2])
if __name__ == "__main__":
main()

73
2018/d9.py Normal file
View File

@@ -0,0 +1,73 @@
from lib import str_to_ints
from collections import defaultdict
def part_1(data):
scores = defaultdict(int)
n_players, worth = str_to_ints(data)
current_player = 0
circle = [0]
current = 1
for n in range(1, worth + 1):
if n % 23 == 0:
scores[current_player] += n
current = (current - 7) % len(circle)
scores[current_player] += circle.pop(current)
else:
current = (current + 2) % len(circle)
circle.insert(current, n)
current_player = (current_player + 1) % n_players
print(max(scores.values()))
class Node:
def __init__(self, value, prev=None, next=None):
self.value = value
if prev is None:
self.prev = self
else:
self.prev = prev
if next is None:
self.next = self
else:
self.next = next
def part_2(data):
scores = defaultdict(int)
n_players, worth = str_to_ints(data)
current_player = 0
first = Node(0)
current = first
current.next = current
current.prev = current
for n in range(1, (worth * 100) + 1):
if n % 23 == 0:
scores[current_player] += n
for _ in range(7):
current = current.prev
scores[current_player] += current.value
current.prev.next = current.next
current.next.prev = current.prev
current = current.next
else:
new_current = Node(n, current.next, current.next.next)
current.next.next.prev = new_current
current.next.next = new_current
current = new_current
current_player = (current_player + 1) % n_players
print(max(scores.values()))
def main():
input_file = __file__.replace(".py", ".txt")
with open(input_file) as f:
data = f.read()
part_1(data)
part_2(data)
if __name__ == "__main__":
main()

1
2018/lib.py Symbolic link
View File

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

1
2018/monitor.py Symbolic link
View File

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

31
2019/d1.py Normal file
View File

@@ -0,0 +1,31 @@
from lib import get_data
def f(i):
i //= 3
i -= 2
return i
def part_1(data):
print(sum([f(int(line)) for line in data.splitlines()]))
def part_2(data):
r = 0
for line in data.splitlines():
i = f(int(line))
while i > 0:
r += i
i = f(i)
print(r)
def main():
data = get_data(__file__)
part_1(data)
part_2(data)
if __name__ == "__main__":
main()

102
2019/d10.py Normal file
View File

@@ -0,0 +1,102 @@
from lib import get_data, Grid2D
from math import gcd, atan2, pi
def angle(a1, a2):
dy = a1[0] - a2[0]
dx = a2[1] - a1[1]
a = (atan2(dx, dy) + 2 * pi) % (2 * pi)
return a
def dist(a1, a2):
return abs(a1[0] - a2[0]) + abs(a1[1] - a2[1])
def part_1(data):
g = Grid2D(data)
asts = g.find("#")
asts_set = set(asts)
best_ast = None
visible_max = 0
for a in asts:
visible = 0
for other in asts:
if a == other:
continue
x1, x2 = a[0], other[0]
y1, y2 = a[1], other[1]
if y1 == y2:
for x in range(min(x1, x2) + 1, max(x1, x2)):
if (x, y1) in asts_set:
break
else:
visible += 1
elif x1 == x2:
for y in range(min(y1, y2) + 1, max(y1, y2)):
if (x1, y) in asts_set:
break
else:
visible += 1
else:
dx = abs(x1 - x2)
dy = abs(y1 - y2)
g = gcd(dx, dy)
dx //= g
dy //= g
x, y = x1, y1
if x2 < x1:
dx = -dx
if y2 < y1:
dy = -dy
blocked = False
while True:
x += dx
y += dy
if (x, y) == (x2, y2):
break
if (x, y) in asts_set:
blocked = True
break
if not blocked:
visible += 1
if visible > visible_max:
visible_max = visible
best_ast = a
assert best_ast is not None
print(visible_max)
other_asts = [a for a in asts if a != best_ast]
removed_count = 0
while True:
removed = set()
other_asts = sorted(
other_asts, key=lambda a: (angle(best_ast, a), dist(best_ast, a))
)
lowest_angle = -1
for a in other_asts:
current_angle = angle(best_ast, a)
if current_angle > lowest_angle:
removed.add(a)
lowest_angle = current_angle
removed_count += 1
if removed_count == 200:
print(a[1] * 100 + a[0])
return
other_asts = [a for a in other_asts if a not in removed]
def main():
data = get_data(__file__)
part_1(data)
if __name__ == "__main__":
main()

93
2019/d11.py Normal file
View File

@@ -0,0 +1,93 @@
from lib import get_data, str_to_ints, add2
from d9 import Amp
DIRS = [(-1, 0), (0, 1), (1, 0), (0, -1)]
BLACK = 0
WHITE = 1
def part_1(data):
xs = str_to_ints(data)
a = Amp(xs)
current_dir_idx = 0
pos = (0, 0)
white_panels = set()
got_painted = set()
while not a.done:
if pos in white_panels:
a.feed(WHITE)
else:
a.feed(BLACK)
a.go()
a.go()
while a.outputs:
paint_color = a.pop()
if paint_color == BLACK:
white_panels.remove(pos)
elif paint_color == WHITE:
white_panels.add(pos)
else:
assert False
got_painted.add(pos)
turn_dir_code = a.pop()
turn_dir = -1 if turn_dir_code == 0 else 1
current_dir_idx = (current_dir_idx + turn_dir) % len(DIRS)
pos = add2(pos, DIRS[current_dir_idx])
print(len(got_painted))
def part_2(data):
xs = str_to_ints(data)
a = Amp(xs)
current_dir_idx = 0
pos = (0, 0)
white_panels = set([pos])
got_painted = set()
while not a.done:
if pos in white_panels:
a.feed(WHITE)
else:
a.feed(BLACK)
a.go()
a.go()
while a.outputs:
paint_color = a.pop()
if paint_color == BLACK:
if pos in white_panels:
white_panels.remove(pos)
elif paint_color == WHITE:
white_panels.add(pos)
else:
assert False
got_painted.add(pos)
turn_dir_code = a.pop()
turn_dir = -1 if turn_dir_code == 0 else 1
current_dir_idx = (current_dir_idx + turn_dir) % len(DIRS)
pos = add2(pos, DIRS[current_dir_idx])
white_panels = [(p[1], p[0]) for p in white_panels]
xs = [p[0] for p in white_panels]
ys = [p[1] for p in white_panels]
white_panels_set = set(white_panels)
x_min, x_max = min(xs), max(xs)
y_min, y_max = min(ys), max(ys)
for y in range(y_min, y_max + 1):
s = ""
for x in range(x_min, x_max + 1):
if (x, y) in white_panels_set:
s += "X"
else:
s += " "
print(s)
def main():
data = get_data(__file__)
part_1(data)
part_2(data)
if __name__ == "__main__":
main()

87
2019/d12.py Normal file
View File

@@ -0,0 +1,87 @@
from lib import get_data, str_to_ints
from math import lcm
def freeze(coords, velos):
coords = tuple(map(tuple, coords))
velos = tuple(map(tuple, velos))
return coords + velos
def part_1(data):
coords = []
velos = []
for line in data.splitlines():
coords.append(str_to_ints(line))
velos.append([0, 0, 0])
for _ in range(1_000):
# apply gravity
for i in range(len(coords)):
for j in range(i + 1, len(coords)):
for d in range(3):
if coords[i][d] > coords[j][d]:
velos[i][d] -= 1
velos[j][d] += 1
elif coords[i][d] < coords[j][d]:
velos[i][d] += 1
velos[j][d] -= 1
# update coords
for i in range(len(coords)):
for d in range(3):
coords[i][d] += velos[i][d]
r = 0
for i in range(len(coords)):
p = sum(map(abs, coords[i]))
k = sum(map(abs, velos[i]))
r += p * k
print(r)
def part_2(data):
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():
data = get_data(__file__)
part_1(data)
part_2(data)
if __name__ == "__main__":
main()

50
2019/d13.py Normal file
View File

@@ -0,0 +1,50 @@
from lib import get_data, str_to_ints
from d9 import Amp
def part_1(data):
xs = str_to_ints(data)
a = Amp(xs)
while not a.done:
a.go()
r = sum([a.outputs[i : i + 3][2] == 2 for i in range(0, len(a.outputs), 3)])
print(r)
def part_2(data):
xs = str_to_ints(data)
xs[0] = 2 # play for free
a = Amp(xs)
ball_x = 0
paddle_x = 0
score = 0
while not a.done:
a.go()
if len(a.outputs) == 3:
x, y, tile_id = a.pop(), a.pop(), a.pop()
if x == -1 and y == 0:
score = tile_id
elif tile_id == 4:
ball_x = x
elif tile_id == 3:
paddle_x = x
if a.input_required:
a.input_required = False
x = 0
if ball_x < paddle_x:
x = -1
elif ball_x > paddle_x:
x = 1
a.feed(x)
print(score)
def main():
data = get_data(__file__)
part_1(data)
part_2(data)
if __name__ == "__main__":
main()

83
2019/d14.py Normal file
View File

@@ -0,0 +1,83 @@
from lib import get_data
from math import ceil
def ore_for_fuel(fuel_n, reactions_store):
ore_qty = 0
required = [(fuel_n, "FUEL")]
store = {}
while required:
required_qty, required_code = required.pop()
if required_code == "ORE":
ore_qty += required_qty
continue
if required_code in store:
if store[required_code] == required_qty:
del store[required_code]
continue
elif store[required_code] > required_qty:
store[required_code] -= required_qty
continue
elif store[required_code] < required_qty:
required_qty -= store[required_code]
del store[required_code]
inputs, output = reactions_store[required_code]
output_qty, output_code = output
assert required_code == output_code
# how often must this process be executed
n = ceil(required_qty / output_qty)
leftover_output_qty = n * output_qty - required_qty
assert leftover_output_qty >= 0
if leftover_output_qty > 0:
store[output_code] = leftover_output_qty
for qty_input, input_code in inputs:
required.append((qty_input * n, input_code))
return ore_qty
def part_1(data):
reactions = []
to_get = {}
for line in data.splitlines():
left, right = line.split(" => ")
inputs = left.split(", ")
inputs = [(int(i.split()[0]), i.split()[1]) for i in inputs]
output = (int(right.split()[0]), right.split()[1])
reactions.append((inputs, output))
if output[1] in to_get:
assert False
else:
to_get[output[1]] = (inputs, output)
ore_qty = ore_for_fuel(1, to_get)
print(ore_qty)
min_fuel, max_fuel = 1, 100_000_000
target = 10**12 # trillion
while max_fuel - min_fuel > 1:
half_fuel = min_fuel + (max_fuel - min_fuel) // 2
ore_qty = ore_for_fuel(half_fuel, to_get)
if ore_qty < target:
min_fuel = half_fuel
elif ore_qty > target:
max_fuel = half_fuel
else:
min_fuel = half_fuel
max_fuel = half_fuel
print(min_fuel)
def main():
data = get_data(__file__)
part_1(data)
if __name__ == "__main__":
main()

115
2019/d15.py Normal file
View File

@@ -0,0 +1,115 @@
from lib import get_data, str_to_ints, add2
from d9 import Amp
DIRS = {1: (-1, 0), 2: (1, 0), 3: (0, -1), 4: (0, 1)}
PATH_REVERSED = {1: 2, 2: 1, 3: 4, 4: 3}
def print_field(fields, walls, pos):
dim = 40
for y in range(-dim, dim + 1):
row = ""
for x in range(-dim, dim + 1):
if (y, x) == pos:
row += "X"
elif (y, x) in fields:
row += "."
elif (y, x) in walls:
row += "|"
else:
row += " "
print(row)
def part_1(data):
xs = str_to_ints(data)
a = Amp(xs)
walls = set()
pos = (0, 0)
target = None
dir = None
path = []
dir_cmds_for_pos = {pos: [1, 2, 3, 4]}
while not a.done:
if len(dir_cmds_for_pos[pos]) > 0:
dir_cmd = dir_cmds_for_pos[pos].pop()
dir = DIRS[dir_cmd]
new_pos = add2(pos, dir)
if new_pos in dir_cmds_for_pos:
continue
a.feed(dir_cmd)
a.go()
status = a.pop()
if status == 0:
walls.add(new_pos)
elif status == 1:
pos = new_pos
path.append(dir_cmd)
elif status == 2:
pos = new_pos
path.append(dir_cmd)
target = pos
if pos not in dir_cmds_for_pos:
dir_cmds_for_pos[pos] = [1, 2, 3, 4]
elif len(path) > 0:
dir_cmd = PATH_REVERSED[path.pop()]
dir = DIRS[dir_cmd]
pos = add2(pos, dir)
a.feed(dir_cmd)
a.go()
status = a.pop()
assert status == 1 or status == 2
elif target is not None:
break
else:
break
fields = set(dir_cmds_for_pos.keys())
# print_field(fields, walls, pos)
seen = set()
to_visit = [(0, 0)]
steps = 0
while len(to_visit) > 0:
new = []
for current in to_visit:
if current == target:
print(steps)
seen.add(current)
for dir in DIRS.values():
nb = add2(current, dir)
if nb in fields and nb not in seen:
new.append(nb)
to_visit = new
steps += 1
seen = set()
to_visit = [target]
steps = 0
while len(to_visit) > 0:
new = []
for current in to_visit:
seen.add(current)
for dir in DIRS.values():
nb = add2(current, dir)
if nb in fields and nb not in seen:
new.append(nb)
to_visit = new
steps += 1
print(steps - 1)
def main():
data = get_data(__file__)
part_1(data)
if __name__ == "__main__":
main()

132
2019/d16.py Normal file
View File

@@ -0,0 +1,132 @@
from lib import get_data
def part_1_with_numpy(data):
import numpy as np
def make_base_matrix(pattern, n):
xss = []
for round in range(n):
xs = [pattern[((i + 1) // (round + 1)) % len(pattern)] for i in range(n)]
xss.append(xs)
return np.array(xss)
pattern = [0, 1, 0, -1]
input = int(data.strip())
v = np.array(list(map(int, str(input))))
m = make_base_matrix(pattern, len(v))
func = np.vectorize(lambda x: abs(x) % 10)
for _ in range(100):
v = func(np.dot(m, v))
print("".join(map(str, v[:8].tolist())))
def phase(digits_in):
pattern = [0, 1, 0, -1]
digits_out = []
for round in range(len(digits_in)):
i, out = 0, 0
while i < len(digits_in):
pattern_i = ((i + 1) // (round + 1)) % len(pattern)
out += pattern[pattern_i] * digits_in[i]
i += 1
out = abs(out) % 10
digits_out.append(out)
return digits_out
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)
pattern_value = pattern[((i + 1) // (round + 1)) % len(pattern)]
if pattern_value == 0:
i += round
while i < len(digits_in):
pattern_i = ((i + 1) // (round + 1)) % len(pattern)
pattern_value = pattern[pattern_i]
if pattern_value != 0:
out += pattern_value * sum(
digits_in[i : min(i + 1 + round, len(digits_in))]
)
i += round + 1
out = abs(out) % 10
digits_out[round] = out
return digits_out
def part_1(data):
pattern = [0, 1, 0, -1]
input = list(map(int, (data.strip())))
for _ in range(100):
input = phase_with_offset(input, pattern, 0)
print("".join(map(str, input[:8])))
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])))
# 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.
# 2. The solution is not crazy. It will be rather obvious.
# 3. 6_500_000 * 6_500_000 is definitely too much to brute force.
# 4. Can we go from O(N^2) to O(N) somehow? Yes, that's what we have to do.
# The whole point of FFT is to get from O(N^2) to O(N*log(N)). Now,
# how exactly do we do that?
#
# Ways to improve performance:
#
# 1. Speed up `phase` significantly. Yes, but how?
# 2. Only compute a subset of the lists? - No!
# 3. Discover some kind of pattern? - No!
#
# Assumptions:
#
# 1. I need every digit of the previous round. - False!
# 2. I cannot just operate on a subset. - False!
#
# Non-approaches:
#
# 1. Fancy recursive algorithm that selectively picks fields.
# 2. Pattern detection or subset consideration.
def main():
data = get_data(__file__)
# part_1_with_numpy(data)
part_1(data)
if __name__ == "__main__":
main()

136
2019/d17.py Normal file
View File

@@ -0,0 +1,136 @@
from lib import get_data, str_to_ints, Grid2D, add2
from collections import defaultdict
from d9 import Amp
DIRS = [
(-1, 0),
(0, 1),
(1, 0),
(0, -1),
]
DIRCHAR = list("^>v<")
def find_path(g):
(pos,) = g.find("^><v")
assert g[pos] == "^"
to_visit = set(g.find("#"))
seen = defaultdict(int)
dir = DIRS[0] # up
seen[pos] = 1
def best_neighbor(pos):
best_nb = None
seen_count = 20
for nb in g.neighbors_ort(pos):
if nb in to_visit:
return nb
if nb in seen and seen[nb] < seen_count:
seen_count = seen[nb]
best_nb = nb
assert best_nb is not None
return best_nb
path = ""
while to_visit:
new_pos = add2(pos, dir)
if new_pos in to_visit or (new_pos in seen and seen[new_pos] < 2):
path += "F"
g[pos] = "#"
pos = new_pos
if pos in to_visit:
to_visit.remove(pos)
g[pos] = DIRCHAR[DIRS.index(dir)]
seen[pos] += 1
else:
best_nb = best_neighbor(pos)
while add2(pos, dir) != best_nb:
path += "R"
dir = DIRS[(DIRS.index(dir) + 1) % len(DIRS)]
g[pos] = DIRCHAR[DIRS.index(dir)]
# For debugging:
# g.print()
# input()
# print()
path = path.replace("RRR", "L")
return path
def part_1(data):
xs = str_to_ints(data)
a = Amp(xs)
text = ""
while not a.done:
a.go()
while a.outputs:
o = a.pop()
text += chr(o)
result = 0
g = Grid2D(text)
for r in range(g.n_rows):
for c in range(g.n_cols):
nbs = g.neighbors_ort((r, c))
if (
g[(r, c)] == "#"
and len(nbs) == 4
and all([g[(nr, nc)] == "#" for nr, nc in nbs])
):
# g[(r, c)] = 'o'
result += r * c
print(result)
# g.print()
# 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():
data = get_data(__file__)
part_1(data)
if __name__ == "__main__":
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()

52
2019/d19.py Normal file
View File

@@ -0,0 +1,52 @@
from lib import get_data, str_to_ints
from d9 import Amp
def isin(xs, x, y):
a = Amp(xs, 100)
a.feed(x)
a.feed(y)
a.go()
return a.pop()
def part_1(data):
xs = str_to_ints(data)
r = 0
for y in range(50):
for x in range(50):
o = isin(xs, x, y)
if o == 1:
print("#", end="")
else:
print(" ", end="")
r += o
print()
print(r)
def part_2(data):
xs = str_to_ints(data)
off = 99
x, y = 3, 4
while True:
y += 1
while True:
o = isin(xs, x, y)
if o == 1:
break
x += 1
if isin(xs, x, y - off) and isin(xs, x + off, y - off) and isin(xs, x + off, y):
print(x * 10_000 + y - off)
break
def main():
data = get_data(__file__)
part_1(data)
part_2(data)
if __name__ == "__main__":
main()

41
2019/d2.py Normal file
View File

@@ -0,0 +1,41 @@
from lib import get_data, str_to_ints
def part_1(data):
xs = str_to_ints(data)
xs[1] = 12
xs[2] = 2
for i in range(0, len(xs), 4):
match xs[i]:
case 1:
xs[xs[i + 3]] = xs[xs[i + 1]] + xs[xs[i + 2]]
case 2:
xs[xs[i + 3]] = xs[xs[i + 1]] * xs[xs[i + 2]]
print(xs[0])
def part_2(data):
for noun in range(1, 101):
for verb in range(1, 101):
xs = str_to_ints(data)
xs[1] = noun
xs[2] = verb
for i in range(0, len(xs), 4):
match xs[i]:
case 1:
xs[xs[i + 3]] = xs[xs[i + 1]] + xs[xs[i + 2]]
case 2:
xs[xs[i + 3]] = xs[xs[i + 1]] * xs[xs[i + 2]]
if xs[0] == 19690720:
print(100 * noun + verb)
return
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
2019/d3.py Normal file
View File

@@ -0,0 +1,57 @@
from lib import get_data, add2
DIRS = {"R": (0, 1), "U": (-1, 0), "D": (1, 0), "L": (0, -1)}
def part_1(data):
xss = []
for path in data.splitlines():
xs = [(0, 0)]
for word in path.split(","):
dir = DIRS[word[0]]
x = int(word[1:])
for _ in range(x):
xs.append(add2(xs[-1], dir))
xss.append(xs)
ys = set(xss[0]) & set(xss[1])
ys.remove((0, 0))
min_dist = 10**9
for y in ys:
d = abs(y[0]) + abs(y[1])
min_dist = min(min_dist, d)
print(min_dist)
def part_2(data):
xss = []
for path in data.splitlines():
pos = (0, 0)
steps = 0
xs = {}
for word in path.split(","):
dir = DIRS[word[0]]
x = int(word[1:])
for _ in range(x):
steps += 1
pos = add2(pos, dir)
if not pos in xs:
xs[pos] = steps
xss.append(xs)
min_steps = 10**9
for x in xss[0].keys():
if x in xss[1]:
steps = xss[0][x] + xss[1][x]
min_steps = min(steps, min_steps)
print(min_steps)
def main():
data = get_data(__file__)
part_1(data)
part_2(data)
if __name__ == "__main__":
main()

70
2019/d4.py Normal file
View File

@@ -0,0 +1,70 @@
from lib import get_data, str_to_ints
def in_range_1(x):
contains_double = False
s = str(x)
for i in range(len(s) - 1):
if s[i] == s[i + 1]:
contains_double = True
break
if not contains_double:
return False
for i in range(len(s) - 1):
if ord(s[i]) > ord(s[i + 1]):
return False
return True
def in_range_2(x):
contains_double = False
s = str(x)
for i in range(len(s) - 1):
if s[i] == s[i + 1]:
if i + 2 < len(s):
if s[i + 2] == s[i + 1]:
continue
if i - 1 >= 0:
if s[i - 1] == s[i]:
continue
contains_double = True
if not contains_double:
return False
for i in range(len(s) - 1):
if ord(s[i]) > ord(s[i + 1]):
return False
return True
def part_1(data):
a, b = str_to_ints(data)
b = -b
r = 0
for x in range(a, b + 1):
if in_range_1(x):
r += 1
print(r)
def part_2(data):
a, b = str_to_ints(data)
b = -b
r = 0
for x in range(a, b + 1):
if in_range_2(x):
r += 1
print(r)
def main():
data = get_data(__file__)
part_1(data)
part_2(data)
if __name__ == "__main__":
main()

123
2019/d5.py Normal file
View File

@@ -0,0 +1,123 @@
from lib import get_data, str_to_ints
def part_1(data):
xs = str_to_ints(data)
i = 0
while i < len(xs):
inst = str(xs[i])
inst = "0" * (5 - len(inst)) + inst
assert len(inst) == 5
op = int(inst[3:5])
mode_p1 = int(inst[2])
mode_p2 = int(inst[1])
mode_p3 = int(inst[0])
match op:
case 1:
p1 = xs[xs[i + 1]] if mode_p1 == 0 else xs[i + 1]
p2 = xs[xs[i + 2]] if mode_p2 == 0 else xs[i + 2]
assert mode_p3 == 0
xs[xs[i + 3]] = p1 + p2
i += 4
case 2:
p1 = xs[xs[i + 1]] if mode_p1 == 0 else xs[i + 1]
p2 = xs[xs[i + 2]] if mode_p2 == 0 else xs[i + 2]
assert mode_p3 == 0
xs[xs[i + 3]] = p1 * p2
i += 4
case 3:
print("input", i, 1)
assert mode_p1 == 0
xs[xs[i + 1]] = 1
i += 2
case 4:
if mode_p1 == 0:
v = xs[xs[i + 1]]
else:
v = xs[i + 1]
print("output", v)
i += 2
case 99:
break
def part_2(data):
xs = str_to_ints(data)
i = 0
while i < len(xs):
inst = str(xs[i])
inst = "0" * (5 - len(inst)) + inst
assert len(inst) == 5
op = int(inst[3:5])
mode_p1 = int(inst[2])
mode_p2 = int(inst[1])
mode_p3 = int(inst[0])
match op:
case 1:
p1 = xs[xs[i + 1]] if mode_p1 == 0 else xs[i + 1]
p2 = xs[xs[i + 2]] if mode_p2 == 0 else xs[i + 2]
assert mode_p3 == 0
xs[xs[i + 3]] = p1 + p2
i += 4
case 2:
p1 = xs[xs[i + 1]] if mode_p1 == 0 else xs[i + 1]
p2 = xs[xs[i + 2]] if mode_p2 == 0 else xs[i + 2]
assert mode_p3 == 0
xs[xs[i + 3]] = p1 * p2
i += 4
case 3:
print("input", i, 5)
assert mode_p1 == 0
xs[xs[i + 1]] = 5
i += 2
case 4:
if mode_p1 == 0:
v = xs[xs[i + 1]]
else:
v = xs[i + 1]
print("output", v)
i += 2
case 99:
break
case 5:
p1 = xs[xs[i + 1]] if mode_p1 == 0 else xs[i + 1]
p2 = xs[xs[i + 2]] if mode_p2 == 0 else xs[i + 2]
if p1 != 0:
i = p2
else:
i += 3
case 6:
p1 = xs[xs[i + 1]] if mode_p1 == 0 else xs[i + 1]
p2 = xs[xs[i + 2]] if mode_p2 == 0 else xs[i + 2]
if p1 == 0:
i = p2
else:
i += 3
case 7:
p1 = xs[xs[i + 1]] if mode_p1 == 0 else xs[i + 1]
p2 = xs[xs[i + 2]] if mode_p2 == 0 else xs[i + 2]
assert mode_p3 == 0
if p1 < p2:
xs[xs[i + 3]] = 1
else:
xs[xs[i + 3]] = 0
i += 4
case 8:
p1 = xs[xs[i + 1]] if mode_p1 == 0 else xs[i + 1]
p2 = xs[xs[i + 2]] if mode_p2 == 0 else xs[i + 2]
assert mode_p3 == 0
if p1 == p2:
xs[xs[i + 3]] = 1
else:
xs[xs[i + 3]] = 0
i += 4
def main():
data = get_data(__file__)
part_1(data)
part_2(data)
if __name__ == "__main__":
main()

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