Compare commits

...

111 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
155 changed files with 9565 additions and 699 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:
print(data)
break break
print(data)

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: else:
r += d t += st
return r else:
for o in obj.values():
t += xsum(o, part_2)
elif type(obj) is str:
pass
else:
print(obj)
assert False
return t
data = json.loads(data)
print(addup(data)) print(xsum(o))
print(xsum(o, True))

View File

@@ -1,35 +1,32 @@
from lib import get_data
from collections import defaultdict
from itertools import permutations 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,19 +1,17 @@
from lib import * from lib import get_data, ints
data = open(0).readlines() data = get_data(__file__)
part_1 = True a = 0
if part_1: for line in data.splitlines():
a = 0 l, w, h = ints(line)
for line in data:
l, w, h = list(map(int, line.split("x")))
slack = min([l * w, w * h, h * l]) slack = min([l * w, w * h, h * l])
a += (2*l*w + 2*w*h + 2*h*l + slack) a += 2 * l * w + 2 * w * h + 2 * h * l + slack
else: print(a)
a = 0
for line in data: a = 0
l, w, h = list(map(int, line.split("x"))) for line in data.splitlines():
l, w, h = ints(line)
sd = min([2 * (l + w), 2 * (w + h), 2 * (h + l)]) sd = min([2 * (l + w), 2 * (w + h), 2 * (h + l)])
a += sd + (l * w * h) 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:

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,16 +1,10 @@
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:
digits = 6
data = open(0).read().strip()
for i in range(10**9):
text = data + str(i) text = data + str(i)
md5_hash = hashlib.md5(text.encode()).hexdigest() md5_hash = hashlib.md5(text.encode()).hexdigest()
for c in md5_hash[:digits]: for c in md5_hash[:digits]:
@@ -19,4 +13,3 @@ for i in range(10**9):
else: else:
print(i) print(i)
break 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: def is_nice(line):
res = 0 # It contains at least three vowels (aeiou only), like aei, xazegov, or aeiouaeiouaeiou.
for line in data.splitlines():
vc = 0 vc = 0
for c in line: for v in "aeiou":
if c in "aoeui": vc += line.count(v)
vc += 1
if vc < 3: if vc < 3:
continue return False
prev = None # It contains at least one letter that appears twice in a row, like xx,
for c in line: # abcdde (dd), or aabbccdd (aa, bb, cc, or dd).
if c == prev: for i in range(len(line) - 1):
break if line[i] == line[i + 1]:
prev = c
else:
continue
contains = False
ss = ["ab", "cd", "pq", "xy"]
for s in ss:
if s in line:
contains = True
if contains:
continue
res += 1
print(res)
else:
res = 0
for line in data.splitlines():
pairs = {}
for i in range(0, len(line) - 1):
p = line[i:i+2]
if p in pairs and i > pairs[p] + 1:
break
if not p in pairs:
pairs[p] = i
else:
continue
for i in range(0, len(line) - 2):
if line[i] == line[i + 2]:
break break
else: else:
continue return False
res += 1
print(res) # It does not contain the strings ab, cd, pq, or xy, even if they are
# part of one of the other requirements.
for i in range(len(line) - 1):
cc = "".join(line[i : i + 2])
if cc in ["ab", "cd", "pq", "xy"]:
return False
return True
def is_nice_2(line):
good = False
for i in range(len(line) - 1):
cc = "".join(line[i : i + 2])
for j in range(i + 2, len(line) - 1):
dd = "".join(line[j : j + 2])
if cc == dd:
good = True
if not good:
return False
for i in range(len(line) - 2):
cc = "".join(line[i : i + 3])
if cc[0] == cc[2]:
break
else:
return False
return True
t = sum(1 if is_nice(line) else 0 for line in data.splitlines())
print(t)
t = sum(1 if is_nice_2(line) else 0 for line in data.splitlines())
print(t)

View File

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

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]
"""

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

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

57
2019/d6.py Normal file
View File

@@ -0,0 +1,57 @@
from lib import get_data, str_to_ints
from collections import defaultdict
def part_1(data):
orbits = defaultdict(list)
for line in data.splitlines():
a, b = line.split(")")
orbits[a].append(b)
count = 0
for key in orbits.keys():
seen = set()
around = list(orbits[key])
while around:
a = around.pop()
count += 1
if a in orbits:
for o in orbits[a]:
if o not in seen:
around.append(o)
seen.add(a)
print(count)
def part_2(data):
g = defaultdict(list)
for line in data.splitlines():
a, b = line.split(")")
g[a].append(b)
g[b].append(a)
seen = set()
nodes = ["YOU"]
steps = 0
while True:
new_nodes = []
for node in nodes:
seen.add(node)
for nb in g[node]:
if nb not in seen:
new_nodes.append(nb)
if nb == "SAN":
print(steps - 1)
return
nodes = new_nodes
steps += 1
def main():
data = get_data(__file__)
part_1(data)
part_2(data)
if __name__ == "__main__":
main()

144
2019/d7.py Normal file
View File

@@ -0,0 +1,144 @@
from lib import get_data, str_to_ints
from itertools import permutations
class Amp:
def __init__(self, xs):
self.xs = list(xs)
self.i = 0
self.inputs = []
self.outputs = []
self.done = False
def feed(self, input):
self.inputs.append(input)
def pop(self):
v = self.outputs[0]
self.outputs = self.outputs[1:]
return v
def go(self):
xs = self.xs
i = self.i
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:
assert mode_p1 == 0
assert len(self.inputs) > 0
xs[xs[i + 1]] = self.inputs[0]
self.inputs = self.inputs[1:]
i += 2
case 4:
if mode_p1 == 0:
v = xs[xs[i + 1]]
else:
v = xs[i + 1]
self.outputs.append(v)
i += 2
self.i = i
return
case 99:
self.done = True
return
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
self.i = i
def part_1(data):
xs_orig = str_to_ints(data)
max_output = 0
for ps in permutations(list(range(5))):
current_output = 0
for p in ps:
a = Amp(xs_orig)
a.feed(p)
a.feed(current_output)
a.go()
assert len(a.outputs) == 1
current_output = a.outputs.pop()
max_output = max(max_output, current_output)
print(max_output)
def part_2(data):
xs_orig = str_to_ints(data)
max_output = 0
for ps in permutations(list(range(5, 10))):
amps = [Amp(xs_orig) for _ in range(len(ps))]
for i, p in enumerate(ps):
amps[i].feed(p)
current_output = 0
current_amp_i = 0
while True:
amps[current_amp_i].feed(current_output)
amps[current_amp_i].go()
if amps[current_amp_i].done:
max_output = max(max_output, current_output)
break
assert len(amps[current_amp_i].outputs) == 1
current_output = amps[current_amp_i].outputs.pop()
current_amp_i = (current_amp_i + 1) % len(amps)
print(max_output)
def main():
data = get_data(__file__)
part_1(data)
part_2(data)
if __name__ == "__main__":
main()

70
2019/d8.py Normal file
View File

@@ -0,0 +1,70 @@
from lib import get_data
def part_1(data):
wide = 25
tall = 6
xs = [int(c) for c in data.strip()]
layers = []
i = 0
while i < len(xs):
if i % (wide * tall) == 0:
layers.append([])
layers[-1].append(xs[i])
i += 1
min_zero_layer = None
min_zeros = 10**9
for layer in layers:
zeros = layer.count(0)
if zeros < min_zeros:
min_zeros = zeros
min_zero_layer = layer
assert min_zero_layer is not None
ones = min_zero_layer.count(1)
twos = min_zero_layer.count(2)
print(ones * twos)
def part_2(data):
wide = 25
tall = 6
xs = [int(c) for c in data.strip()]
layers = []
i = 0
while i < len(xs):
if i % (wide * tall) == 0:
layers.append([])
layers[-1].append(xs[i])
i += 1
result = []
for i in range(wide * tall):
current = 2
for layer in layers:
pixel = layer[i]
if current == 2:
current = pixel
result.append(current)
for row in range(tall):
row_str = ""
for col in range(wide):
pixel_idx = row * wide + col
pixel = result[pixel_idx]
if pixel == 0:
row_str += ""
elif pixel == 1:
row_str += ""
else:
assert False
print(row_str)
def main():
data = get_data(__file__)
part_1(data)
part_2(data)
if __name__ == "__main__":
main()

154
2019/d9.py Normal file
View File

@@ -0,0 +1,154 @@
from lib import get_data, str_to_ints
class Amp:
def __init__(self, xs, buffer_extra=10000):
self.xs = list(xs)
self.xs += [0 for _ in range(buffer_extra)]
self.i = 0
self.inputs = []
self.outputs = []
self.done = False
self.input_required = False
self.rel_base = 0
def feed(self, input):
self.input_required = False
self.inputs.append(input)
def pop(self):
v = self.outputs[0]
self.outputs = self.outputs[1:]
return v
def get_param(self, offset, mode):
if mode == 0:
p = self.xs[self.xs[offset]]
elif mode == 1:
p = self.xs[offset]
elif mode == 2:
assert self.rel_base + offset >= 0
p = self.xs[self.rel_base + self.xs[offset]]
else:
assert False
return p
def get_addr(self, offset, mode):
if mode == 0:
return self.xs[offset]
elif mode == 2:
return self.rel_base + self.xs[offset]
else:
assert False
def go(self):
xs = self.xs
i = self.i
while i < len(xs):
assert xs[i] >= 0
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 = self.get_param(i + 1, mode_p1)
p2 = self.get_param(i + 2, mode_p2)
addr = self.get_addr(i + 3, mode_p3)
xs[addr] = p1 + p2
i += 4
case 2:
p1 = self.get_param(i + 1, mode_p1)
p2 = self.get_param(i + 2, mode_p2)
addr = self.get_addr(i + 3, mode_p3)
xs[addr] = p1 * p2
i += 4
case 3:
# read input
if len(self.inputs) == 0:
self.i = i
self.input_required = True
return
addr = self.get_addr(i + 1, mode_p1)
xs[addr] = self.inputs[0]
self.inputs = self.inputs[1:]
i += 2
case 4:
# output
v = self.get_param(i + 1, mode_p1)
self.outputs.append(v)
i += 2
self.i = i
return
case 99:
self.done = True
return
case 5:
p1 = self.get_param(i + 1, mode_p1)
p2 = self.get_param(i + 2, mode_p2)
if p1 != 0:
i = p2
else:
i += 3
case 6:
p1 = self.get_param(i + 1, mode_p1)
p2 = self.get_param(i + 2, mode_p2)
if p1 == 0:
i = p2
else:
i += 3
case 7:
p1 = self.get_param(i + 1, mode_p1)
p2 = self.get_param(i + 2, mode_p2)
addr = self.get_addr(i + 3, mode_p3)
if p1 < p2:
xs[addr] = 1
else:
xs[addr] = 0
i += 4
case 8:
p1 = self.get_param(i + 1, mode_p1)
p2 = self.get_param(i + 2, mode_p2)
addr = self.get_addr(i + 3, mode_p3)
if p1 == p2:
xs[addr] = 1
else:
xs[addr] = 0
i += 4
case 9:
p1 = self.get_param(i + 1, mode_p1)
self.rel_base += p1
i += 2
case _:
assert False
self.i = i
def part_1(data):
xs_orig = str_to_ints(data)
a = Amp(xs_orig)
a.feed(1)
while not a.done:
a.go()
a.go()
print(a.pop())
a = Amp(xs_orig)
a.feed(2)
while not a.done:
a.go()
a.go()
print(a.pop())
def main():
data = get_data(__file__)
part_1(data)
if __name__ == "__main__":
main()

1
2019/lib.py Symbolic link
View File

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

1
2019/monitor.py Symbolic link
View File

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

31
2020/d1.py Normal file
View File

@@ -0,0 +1,31 @@
from lib import get_data, str_to_ints
def part_1(data):
xs = sorted(str_to_ints(data))
for i in range(len(xs)):
for j in range(i + 1, len(xs)):
a, b = xs[i], xs[j]
if a + b == 2020:
print(a * b)
if a + b > 2020:
break
for i in range(len(xs)):
for j in range(i + 1, len(xs)):
for k in range(j + 1, len(xs)):
a, b, c = xs[i], xs[j], xs[k]
if a + b + c == 2020:
print(a * b * c)
if a + b + c > 2020:
break
def main():
data = get_data(__file__)
part_1(data)
if __name__ == "__main__":
main()

57
2020/d10.py Normal file
View File

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

89
2020/d11.py Normal file
View File

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

82
2020/d12.py Normal file
View File

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

39
2020/d13.py Normal file
View File

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

77
2020/d14.py Normal file
View File

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

22
2020/d15.py Normal file
View File

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

88
2020/d16.py Normal file
View File

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

42
2020/d17.py Normal file
View File

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

106
2020/d18.py Normal file
View File

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

62
2020/d19.py Normal file
View File

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

34
2020/d2.py Normal file
View File

@@ -0,0 +1,34 @@
from lib import get_data
def part_1(data):
r = 0
for line in data.splitlines():
pol, pas = line.split(": ")
nums, letter = pol.split(" ")
lo, hi = list(map(int, nums.split("-")))
c = pas.count(letter)
if c >= lo and c <= hi:
r += 1
print(r)
r = 0
for line in data.splitlines():
pol, pas = line.split(": ")
nums, letter = pol.split(" ")
lo, hi = list(map(int, nums.split("-")))
c = pas.count(letter)
if (pas[lo - 1] == letter or pas[hi - 1] == letter) and not (
pas[lo - 1] == letter and pas[hi - 1] == letter
):
r += 1
print(r)
def main():
data = get_data(__file__)
part_1(data)
if __name__ == "__main__":
main()

169
2020/d20.py Normal file
View File

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

50
2020/d21.py Normal file
View File

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

86
2020/d22.py Normal file
View File

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

73
2020/d23.py Normal file
View File

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

59
2020/d24.py Normal file
View File

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

32
2020/d25.py Normal file
View File

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

33
2020/d3.py Normal file
View File

@@ -0,0 +1,33 @@
from lib import get_data, Grid2D
def part_1(data):
g = Grid2D(data)
pos = (0, 0)
r = 0
while pos[0] < g.n_rows:
if g[pos] == "#":
r += 1
pos = (pos[0] + 1, (pos[1] + 3) % g.n_cols)
print(r)
fr = 1
for dr, dc in [(1, 1), (1, 3), (1, 5), (1, 7), (2, 1)]:
pos = (0, 0)
r = 0
while pos[0] < g.n_rows:
if g[pos] == "#":
r += 1
pos = (pos[0] + dr, (pos[1] + dc) % g.n_cols)
fr *= r
print(fr)
def main():
data = get_data(__file__)
part_1(data)
if __name__ == "__main__":
main()

107
2020/d4.py Normal file
View File

@@ -0,0 +1,107 @@
from lib import get_data
required = [
"byr",
"iyr",
"eyr",
"hgt",
"hcl",
"ecl",
"pid",
]
# hgt (Height) - a number followed by either cm or in:
#
# If cm, the number must be at least 150 and at most 193.
# If in, the number must be at least 59 and at most 76.
def part_1(data):
passwords = [""]
for line in data.splitlines():
if line.strip() == "":
passwords.append("")
else:
passwords[-1] = passwords[-1] + " " + line.strip()
valid = 0
for pw in passwords:
for r in required:
if r not in pw:
break
else:
valid += 1
print(valid)
kws = []
for pw in passwords:
d = {}
for word in pw.split():
key, value = word.split(":")
d[key] = value
kws.append(d)
valid = 0
for kw in kws:
for r in required:
if r not in kw:
break
match r:
case "hgt":
v = int(kw[r][:-2])
if kw[r].endswith("cm"):
if v < 150 or v > 193:
break
elif kw[r].endswith("in"):
if v < 59 or v > 76:
break
else:
break
case "byr":
v = int(kw[r])
if v < 1920 or v > 2002:
break
case "iyr":
v = int(kw[r])
if v < 2010 or v > 2020:
break
case "eyr":
v = int(kw[r])
if v < 2020 or v > 2030:
break
case "hcl":
v = kw[r]
if len(v) != 7 or not v.startswith("#"):
break
try:
int(v.replace("#", ""), 16)
except ValueError:
break
case "pid":
v = kw[r]
if len(v) != 9:
break
try:
int(v, 10)
except ValueError:
break
case "ecl":
v = kw[r]
if not v in ["amb", "blu", "brn", "gry", "grn", "hzl", "oth"]:
break
case _:
assert False
else:
valid += 1
print(valid)
def main():
data = get_data(__file__)
part_1(data)
if __name__ == "__main__":
main()

40
2020/d5.py Normal file
View File

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

24
2020/d6.py Normal file
View File

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

47
2020/d7.py Normal file
View File

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

71
2020/d8.py Normal file
View File

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

33
2020/d9.py Normal file
View File

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

1
2020/lib.py Symbolic link
View File

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

1
2020/monitor.py Symbolic link
View File

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

20
2021/d1.py Normal file
View File

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

63
2021/d10.py Normal file
View File

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

37
2021/d11.py Normal file
View File

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

56
2021/d12.py Normal file
View File

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

51
2021/d13.py Normal file
View File

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

43
2021/d14.py Normal file
View File

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

83
2021/d15.py Normal file
View File

@@ -0,0 +1,83 @@
from lib import get_data
from lib import Grid2D
import heapq
data = get_data(__file__)
def new_data(data, repeat=5):
new_lines = []
for line in data.splitlines():
new_line = str(line)
for i in range(len(line) * repeat):
ni = i - len(line)
if ni < 0:
continue
nc = int(new_line[ni]) + 1
if nc == 10:
nc = 1
new_line += str(nc)
new_lines.append(new_line)
for i in range(len(data.splitlines()) * repeat):
ni = i - len(data.splitlines())
if ni < 0:
continue
line = list(new_lines[ni])
for i in range(len(line)):
nc = int(line[i]) + 1
if nc == 10:
nc = 1
line[i] = str(nc)
line = "".join(line)
new_lines.append(line)
data = "\n".join(new_lines)
return data
big_data = new_data(data)
for data in [data, big_data]:
g = Grid2D(data)
def dist(n1, n2):
"""cost from node to node"""
if n1 == 0:
return 0
return int(g[n2])
def h(node):
"""heuristic function (never overestimate)"""
return abs(g.n_rows - 1 - node[0]) + abs(g.n_cols - 1 - node[1])
def is_goal(node):
return node == (g.n_rows - 1, g.n_cols - 1)
def neighbors(node):
return list(g.neighbors_ort(node))
starts = [(0, 0)]
open_set = []
g_score = {}
cost = None
for start in starts:
heapq.heappush(open_set, (h(start), start))
g_score[start] = dist(0, start)
while open_set:
current_f_score, current = heapq.heappop(open_set)
if is_goal(current):
assert current_f_score == g_score[current]
cost = g_score[current]
break
for neighbor in neighbors(current):
tentative_g_score = g_score[current] + dist(current, neighbor)
if neighbor not in g_score or tentative_g_score < g_score[neighbor]:
g_score[neighbor] = tentative_g_score
f_score = g_score[neighbor] + h(neighbor)
heapq.heappush(open_set, (f_score, neighbor))
print(cost)

85
2021/d16.py Normal file
View File

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

35
2021/d17.py Normal file
View File

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

115
2021/d18.py Normal file
View File

@@ -0,0 +1,115 @@
from lib import get_data
from math import ceil, floor
from itertools import combinations
data = """[[[0,[5,8]],[[1,7],[9,6]]],[[4,[1,2]],[[1,4],2]]]
[[[5,[2,8]],4],[5,[[9,9],0]]]
[6,[[[6,2],[5,6]],[[7,6],[4,7]]]]
[[[6,[0,7]],[0,9]],[4,[9,[9,0]]]]
[[[7,[6,4]],[3,[1,3]]],[[[5,5],1],9]]
[[6,[[7,3],[3,2]]],[[[3,8],[5,7]],4]]
[[[[5,4],[7,7]],8],[[8,3],8]]
[[9,3],[[9,9],[6,[4,9]]]]
[[2,[[7,7],7]],[[5,8],[[9,3],[0,2]]]]
[[[[5,2],5],[8,[3,7]]],[[5,[7,5]],[4,4]]]"""
data = get_data(__file__)
def parse(line):
xs = list(line)
for i in range(len(xs)):
try:
xs[i] = int(xs[i])
except ValueError:
pass
assert line == "".join(map(str, xs))
return xs
def reduce(xs):
reduced, nesting = False, 0
for i in range(len(xs)):
c = xs[i]
if c == "[" and nesting == 4 and not reduced:
lhs, rhs = xs[i + 1], xs[i + 3]
if not (type(lhs) is int and type(rhs) is int):
continue
xs = xs[:i] + [0] + xs[i + 5 :]
for j in range(i - 1, -1, -1):
if type(xs[j]) is int:
xs[j] += lhs
break
for j in range(i + 1, len(xs)):
if type(xs[j]) is int:
xs[j] += rhs
break
reduced = True
break
elif c == "[":
nesting += 1
elif c == "]":
nesting -= 1
if not reduced:
for i in range(len(xs)):
c = xs[i]
if type(c) is int and c >= 10:
p = [floor(c / 2), ceil(c / 2)]
xs = xs[:i] + ["[", p[0], ",", p[1], "]"] + xs[i + 1 :]
reduced = True
break
return xs
def restore(xs):
return "".join(map(str, xs))
assert restore(reduce(parse("[[[[[9,8],1],2],3],4]"))) == "[[[[0,9],2],3],4]"
assert restore(reduce(parse("[7,[6,[5,[4,[3,2]]]]]"))) == "[7,[6,[5,[7,0]]]]"
assert (
restore(reduce(parse("[[3,[2,[1,[7,3]]]],[6,[5,[4,[3,2]]]]]")))
== "[[3,[2,[8,0]]],[9,[5,[4,[3,2]]]]]"
)
assert (
restore(reduce(parse("[[3,[2,[8,0]]],[9,[5,[4,[3,2]]]]]")))
== "[[3,[2,[8,0]]],[9,[5,[7,0]]]]"
)
def add(a, b):
s = ["["] + a + [","] + b + ["]"]
while True:
ns = reduce(s)
if s == ns:
break
s = ns
return s
a = "[[[[4,3],4],4],[7,[[8,4],9]]]"
b = "[1,1]"
assert restore(add(parse(a), parse(b))) == "[[[[0,7],4],[[7,8],[6,0]]],[8,1]]"
lines = list(data.splitlines())
s = parse(lines[0].strip())
for line in lines[1:]:
s = add(s, parse(line.strip()))
def get_mag(xs):
if type(xs) is int:
return xs
return get_mag(xs[0]) * 3 + get_mag(xs[1]) * 2
print(get_mag(eval(restore(s))))
lines = list(data.splitlines())
m = 0
for a, b in combinations(lines, 2):
m = max(m, get_mag(eval(restore(add(parse(a), parse(b))))))
m = max(m, get_mag(eval(restore(add(parse(b), parse(a))))))
print(m)

121
2021/d19.py Normal file
View File

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

24
2021/d2.py Normal file
View File

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

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