Compare commits
57 Commits
7d1dc3f95e
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 288a0809ff | |||
| 42e0c68d93 | |||
| 867a7ed2df | |||
| df0682e989 | |||
| e5421854a9 | |||
| 183f909508 | |||
| e655580b3f | |||
| c9145e6106 | |||
| 7013c5b0a0 | |||
| 376a2eac09 | |||
| 2ee16eb1ba | |||
| 8cc199105c | |||
| 3dd208c088 | |||
| 265829715a | |||
| 3979a60fa4 | |||
| acdf82d768 | |||
| f4edfaa680 | |||
| 299f1cf2ff | |||
| 744dbb4ffc | |||
| 4ad7075785 | |||
| 3432d81941 | |||
| efb1e3e551 | |||
| 022b95eb97 | |||
| 96f15d07fc | |||
| 83fbf59bd7 | |||
| e9211c26a3 | |||
| 2a10543852 | |||
| b747d3973a | |||
| b622ca92b9 | |||
| e9a4ce6414 | |||
| 36f8f709e1 | |||
| fae61ae6db | |||
| 63166ddce8 | |||
| 24174b8cb9 | |||
| 0fb75687d1 | |||
| b627e97c5a | |||
| 893a8e3dbb | |||
| 12662b912c | |||
| 54bbc228f4 | |||
| 2d3a44760e | |||
| a2a6ca52d1 | |||
| 4acc229c5a | |||
| ff3d33a614 | |||
| bde0a5d622 | |||
| 9ac4782157 | |||
| 43767f7b62 | |||
| e9bd759dba | |||
| 11bcbce099 | |||
| 074a7c4458 | |||
| 10e1e567c8 | |||
| e73fa3bae7 | |||
| 87ab42743e | |||
| 800d1a6af3 | |||
| 5dc7c4392f | |||
| 67914a642a | |||
| df31b47934 | |||
| 3991842ef0 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -4,6 +4,8 @@
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
.python-version
|
||||
uv.lock
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
16
2015/d1.py
16
2015/d1.py
@@ -1,8 +1,9 @@
|
||||
from lib import *
|
||||
from lib import get_data
|
||||
|
||||
data = open(0).read()
|
||||
data = get_data(__file__)
|
||||
|
||||
part_2 = True
|
||||
t = sum((1 if c == "(" else -1 for c in data))
|
||||
print(t)
|
||||
|
||||
floor = 0
|
||||
for i, c in enumerate(data):
|
||||
@@ -10,13 +11,6 @@ for i, c in enumerate(data):
|
||||
floor += 1
|
||||
elif c == ")":
|
||||
floor -= 1
|
||||
else:
|
||||
print(c)
|
||||
assert False
|
||||
|
||||
if part_2 and floor == -1:
|
||||
if floor == -1:
|
||||
print(i + 1)
|
||||
break
|
||||
|
||||
if not part_2:
|
||||
print(floor)
|
||||
|
||||
54
2015/d10.py
54
2015/d10.py
@@ -1,35 +1,29 @@
|
||||
from functools import lru_cache
|
||||
data = open(0).read().strip()
|
||||
from lib import get_data
|
||||
|
||||
part_1 = False
|
||||
if part_1:
|
||||
repeats = 40
|
||||
else:
|
||||
repeats = 50
|
||||
data = get_data(__file__)
|
||||
|
||||
@lru_cache
|
||||
def look_and_say(data: str) -> str:
|
||||
r = ""
|
||||
i = 0
|
||||
while i < len(data):
|
||||
count = 0
|
||||
c = data[i]
|
||||
while i < len(data) and data[i] == c:
|
||||
|
||||
def iter(xs):
|
||||
if not xs:
|
||||
return []
|
||||
grouped = []
|
||||
current_element = xs[0]
|
||||
count = 1
|
||||
for element in xs[1:]:
|
||||
if element == current_element:
|
||||
count += 1
|
||||
i += 1
|
||||
r += f"{count}{c}"
|
||||
return r
|
||||
else:
|
||||
grouped.append(count)
|
||||
grouped.append(current_element)
|
||||
current_element = element
|
||||
count = 1
|
||||
grouped.append(count)
|
||||
grouped.append(current_element)
|
||||
return grouped
|
||||
|
||||
CHUNK_SIZE = 10000
|
||||
for _ in range(repeats):
|
||||
ndata = ""
|
||||
lo, up = 0, CHUNK_SIZE
|
||||
while up < len(data):
|
||||
while up < len(data) and data[up - 1] == data[up]:
|
||||
up += 1
|
||||
ndata += look_and_say(data[lo:up])
|
||||
lo, up = up, up + CHUNK_SIZE
|
||||
ndata += look_and_say(data[lo:up])
|
||||
data = ndata
|
||||
|
||||
print(len(data))
|
||||
for repeat in [40, 50]:
|
||||
xs = list(map(int, list(data.strip())))
|
||||
for _ in range(repeat):
|
||||
xs = iter(xs)
|
||||
print(len(xs))
|
||||
|
||||
23
2015/d11.py
23
2015/d11.py
@@ -1,4 +1,7 @@
|
||||
data = open(0).read().strip()
|
||||
from lib import get_data
|
||||
|
||||
data = get_data(__file__).strip()
|
||||
|
||||
|
||||
def is_valid(pw):
|
||||
# Passwords must include one increasing straight of at least three letters,
|
||||
@@ -26,24 +29,26 @@ def is_valid(pw):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def inc(pw):
|
||||
pw = list(map(ord, pw))
|
||||
for i in range(len(pw) - 1, -1, -1):
|
||||
pw[i] += 1
|
||||
if pw[i] == ord('z') + 1:
|
||||
pw[i] = ord('a')
|
||||
if pw[i] == ord("z") + 1:
|
||||
pw[i] = ord("a")
|
||||
else:
|
||||
break
|
||||
return "".join(map(chr, pw))
|
||||
|
||||
part_1 = False
|
||||
|
||||
part_1 = True
|
||||
valid_count = 0
|
||||
while True:
|
||||
data = inc(data)
|
||||
if is_valid(data):
|
||||
valid_count += 1
|
||||
if part_1 and valid_count == 1:
|
||||
break
|
||||
elif valid_count == 2:
|
||||
break
|
||||
print(data)
|
||||
if part_1 and valid_count == 1:
|
||||
print(data)
|
||||
elif valid_count == 2:
|
||||
print(data)
|
||||
break
|
||||
|
||||
54
2015/d12.py
54
2015/d12.py
@@ -1,27 +1,37 @@
|
||||
from lib import get_data
|
||||
import json
|
||||
|
||||
data = open(0).read().strip()
|
||||
part_2 = False
|
||||
data = get_data(__file__).strip()
|
||||
|
||||
def addup(d):
|
||||
r = 0
|
||||
if isinstance(d, list):
|
||||
for e in d:
|
||||
v = addup(e)
|
||||
if v is not None:
|
||||
r += v
|
||||
elif isinstance(d, dict):
|
||||
for e in d.values():
|
||||
v = addup(e)
|
||||
if v is None:
|
||||
return 0
|
||||
r += v
|
||||
elif isinstance(d, str):
|
||||
if part_2 and d == "red":
|
||||
return None
|
||||
o = json.loads(data)
|
||||
|
||||
|
||||
def xsum(obj, part_2=False):
|
||||
t = 0
|
||||
if type(obj) is int:
|
||||
t += obj
|
||||
elif type(obj) is list:
|
||||
for o in obj:
|
||||
t += xsum(o, part_2)
|
||||
elif type(obj) is dict:
|
||||
if part_2:
|
||||
st = 0
|
||||
for o in obj.values():
|
||||
st += xsum(o, part_2)
|
||||
if type(o) is str and o == "red":
|
||||
break
|
||||
else:
|
||||
t += st
|
||||
else:
|
||||
for o in obj.values():
|
||||
t += xsum(o, part_2)
|
||||
elif type(obj) is str:
|
||||
pass
|
||||
else:
|
||||
r += d
|
||||
return r
|
||||
print(obj)
|
||||
assert False
|
||||
return t
|
||||
|
||||
data = json.loads(data)
|
||||
print(addup(data))
|
||||
|
||||
print(xsum(o))
|
||||
print(xsum(o, True))
|
||||
|
||||
49
2015/d13.py
49
2015/d13.py
@@ -1,35 +1,32 @@
|
||||
from lib import get_data
|
||||
from collections import defaultdict
|
||||
from itertools import permutations
|
||||
data = open(0).read().strip()
|
||||
|
||||
part_2 = False
|
||||
data = get_data(__file__).strip()
|
||||
|
||||
pairs = defaultdict(int)
|
||||
people = set()
|
||||
scores = {}
|
||||
|
||||
for line in data.splitlines():
|
||||
a, _, wl, score, _, _, _, _, _, _, b = line.split()
|
||||
b = b.replace(".", "")
|
||||
score = int(score)
|
||||
if wl == "lose":
|
||||
score = -score
|
||||
a, _, gainlose, amount, _, _, _, _, _, _, b = line[:-1].split()
|
||||
amount = int(amount)
|
||||
if gainlose == "lose":
|
||||
amount = -amount
|
||||
pairs[(a, b)] = amount
|
||||
people.add(a)
|
||||
people.add(b)
|
||||
scores[(a, b)] = score
|
||||
if part_2:
|
||||
scores[(a, "me")] = 0
|
||||
scores[("me", a)] = 0
|
||||
scores[(b, "me")] = 0
|
||||
scores[("me", b)] = 0
|
||||
|
||||
if part_2:
|
||||
people.add("me")
|
||||
|
||||
max_score = 0
|
||||
for p in permutations(list(people)):
|
||||
s = 0
|
||||
for i in range(len(p)):
|
||||
a, b = p[i], p[(i + 1) % len(p)]
|
||||
s += scores[(a, b)]
|
||||
s += scores[(b, a)]
|
||||
max_score = max(max_score, s)
|
||||
def calc_gain(people):
|
||||
maxgain = 0
|
||||
for xs in permutations(list(people)):
|
||||
gain = 0
|
||||
for i in range(len(xs)):
|
||||
gain += pairs[(xs[i], xs[(i + 1) % len(xs)])]
|
||||
gain += pairs[(xs[(i + 1) % len(xs)], xs[i])]
|
||||
maxgain = max(maxgain, gain)
|
||||
return maxgain
|
||||
|
||||
print(max_score)
|
||||
|
||||
print(calc_gain(people))
|
||||
people.add("Felix")
|
||||
print(calc_gain(people))
|
||||
|
||||
79
2015/d14.py
79
2015/d14.py
@@ -1,53 +1,40 @@
|
||||
import lib
|
||||
data = open(0).read().strip()
|
||||
from lib import get_data
|
||||
from lib import ints, fst
|
||||
from collections import defaultdict
|
||||
|
||||
part_1 = False
|
||||
|
||||
rds = []
|
||||
max_dist = 0
|
||||
gtime = 2503
|
||||
rds = []
|
||||
data = get_data(__file__).strip()
|
||||
deers = []
|
||||
for line in data.splitlines():
|
||||
speed, time, rest = lib.str_to_ints(line)
|
||||
rds.append([speed, time, rest, 0, 0, 0])
|
||||
speed *= 1000
|
||||
speed, time, rest = ints(line)
|
||||
deers.append([0, speed, time, rest, time, 0])
|
||||
|
||||
dist = 0
|
||||
ctime = 0
|
||||
while ctime < gtime:
|
||||
t = min(time, gtime - ctime)
|
||||
dist += (speed * t)
|
||||
ctime += time
|
||||
ctime += rest
|
||||
max_dist = max(dist, max_dist)
|
||||
points = defaultdict(int)
|
||||
for _ in range(2503):
|
||||
max_dists = defaultdict(list)
|
||||
for i, deer in enumerate(deers):
|
||||
dist, speed, time, rest, time_left, rest_left = deer
|
||||
if time_left > 0:
|
||||
time_left -= 1
|
||||
dist += speed
|
||||
elif time_left == 0:
|
||||
time_left = -1
|
||||
rest_left = rest - 1
|
||||
elif rest_left > 0:
|
||||
rest_left -= 1
|
||||
elif rest_left == 0:
|
||||
time_left = time - 1
|
||||
dist += speed
|
||||
rest_left = -1
|
||||
max_dists[dist].append(i)
|
||||
|
||||
if part_1:
|
||||
print(max_dist // 1000)
|
||||
deer[0] = dist
|
||||
deer[4] = time_left
|
||||
deer[5] = rest_left
|
||||
|
||||
scores = [0 for _ in range(len(rds))]
|
||||
for _ in range(gtime):
|
||||
for i in range(len(rds)):
|
||||
speed, time, rest, dist, travel_time, rest_time = rds[i]
|
||||
rd = rds[i]
|
||||
if travel_time < time:
|
||||
rd[3] += speed
|
||||
rd[4] += 1
|
||||
elif rest_time < rest:
|
||||
rd[5] += 1
|
||||
if rd[5] == rest:
|
||||
rd[4] = 0
|
||||
rd[5] = 0
|
||||
max_deers = max(max_dists.items())
|
||||
for di in max_deers[1]:
|
||||
points[di] += 1
|
||||
|
||||
max_i = -1
|
||||
max_v = 0
|
||||
for i, rd in enumerate(rds):
|
||||
if rd[3] > max_v:
|
||||
max_i = i
|
||||
max_v = rd[3]
|
||||
for i, rd in enumerate(rds):
|
||||
if rd[3] == max_v:
|
||||
scores[i] += 1
|
||||
|
||||
if not part_1:
|
||||
print(max(scores))
|
||||
|
||||
print(max(map(fst, deers)))
|
||||
print(max(points.values()))
|
||||
|
||||
30
2015/d2.py
30
2015/d2.py
@@ -1,19 +1,17 @@
|
||||
from lib import *
|
||||
from lib import get_data, ints
|
||||
|
||||
data = open(0).readlines()
|
||||
|
||||
part_1 = True
|
||||
if part_1:
|
||||
a = 0
|
||||
for line in data:
|
||||
l, w, h = list(map(int, line.split("x")))
|
||||
slack = min([l * w, w * h, h * l])
|
||||
a += (2*l*w + 2*w*h + 2*h*l + slack)
|
||||
else:
|
||||
a = 0
|
||||
for line in data:
|
||||
l, w, h = list(map(int, line.split("x")))
|
||||
sd = min([2 * (l + w), 2 * (w + h), 2 * (h + l)])
|
||||
a += sd + (l * w * h)
|
||||
data = get_data(__file__)
|
||||
|
||||
a = 0
|
||||
for line in data.splitlines():
|
||||
l, w, h = ints(line)
|
||||
slack = min([l * w, w * h, h * l])
|
||||
a += 2 * l * w + 2 * w * h + 2 * h * l + slack
|
||||
print(a)
|
||||
|
||||
a = 0
|
||||
for line in data.splitlines():
|
||||
l, w, h = ints(line)
|
||||
sd = min([2 * (l + w), 2 * (w + h), 2 * (h + l)])
|
||||
a += sd + (l * w * h)
|
||||
print(a)
|
||||
|
||||
@@ -4,6 +4,7 @@ import lib
|
||||
data = int(open(0).read().strip())
|
||||
part_1 = False
|
||||
|
||||
|
||||
def calc(n):
|
||||
fs = lib.prime_factors(n)
|
||||
vs = set([1, n])
|
||||
@@ -21,6 +22,7 @@ def calc(n):
|
||||
r += e * 11
|
||||
return r
|
||||
|
||||
|
||||
for i in range(3, 10000000):
|
||||
c = calc(i)
|
||||
if c >= data:
|
||||
|
||||
33
2015/d3.py
33
2015/d3.py
@@ -1,9 +1,7 @@
|
||||
import sys
|
||||
from lib import *
|
||||
from lib import get_data
|
||||
from lib import V
|
||||
|
||||
data = open(0).read()
|
||||
|
||||
part_1 = False
|
||||
data = get_data(__file__)
|
||||
|
||||
DIRS = {
|
||||
"^": (-1, 0),
|
||||
@@ -12,30 +10,27 @@ DIRS = {
|
||||
"<": (0, -1),
|
||||
}
|
||||
|
||||
if part_1:
|
||||
pos = (0, 0)
|
||||
poss = set([pos])
|
||||
pos = V(0, 0)
|
||||
poss = set([pos])
|
||||
|
||||
for c in data:
|
||||
d = DIRS[c]
|
||||
pos = pos[0] + d[0], pos[1] + d[1]
|
||||
poss.add(pos)
|
||||
for c in data:
|
||||
d = DIRS[c]
|
||||
pos = pos + d
|
||||
poss.add(pos)
|
||||
|
||||
print(len(poss))
|
||||
sys.exit(0)
|
||||
print(len(poss))
|
||||
|
||||
a = (0, 0)
|
||||
b = (0, 0)
|
||||
a = V(0, 0)
|
||||
b = V(0, 0)
|
||||
poss = set([a, b])
|
||||
for i, c in enumerate(data):
|
||||
if i % 2 == 0:
|
||||
d = DIRS[c]
|
||||
a = a[0] + d[0], a[1] + d[1]
|
||||
a = a + d
|
||||
poss.add(a)
|
||||
else:
|
||||
d = DIRS[c]
|
||||
b = b[0] + d[0], b[1] + d[1]
|
||||
b = b + d
|
||||
poss.add(b)
|
||||
|
||||
print(len(poss))
|
||||
sys.exit(0)
|
||||
|
||||
29
2015/d4.py
29
2015/d4.py
@@ -1,22 +1,15 @@
|
||||
from lib import *
|
||||
from lib import get_data
|
||||
import hashlib
|
||||
|
||||
part_1 = True
|
||||
data = get_data(__file__).strip()
|
||||
|
||||
if part_1:
|
||||
digits = 5
|
||||
else:
|
||||
digits = 6
|
||||
|
||||
data = open(0).read().strip()
|
||||
|
||||
for i in range(10**9):
|
||||
text = data + str(i)
|
||||
md5_hash = hashlib.md5(text.encode()).hexdigest()
|
||||
for c in md5_hash[:digits]:
|
||||
if c != "0":
|
||||
for digits in [5, 6]:
|
||||
for i in range(10**9):
|
||||
text = data + str(i)
|
||||
md5_hash = hashlib.md5(text.encode()).hexdigest()
|
||||
for c in md5_hash[:digits]:
|
||||
if c != "0":
|
||||
break
|
||||
else:
|
||||
print(i)
|
||||
break
|
||||
else:
|
||||
print(i)
|
||||
break
|
||||
|
||||
|
||||
97
2015/d5.py
97
2015/d5.py
@@ -1,53 +1,56 @@
|
||||
from lib import *
|
||||
from lib import get_data
|
||||
|
||||
data = open(0).read()
|
||||
data = get_data(__file__)
|
||||
|
||||
part_1 = True
|
||||
if part_1:
|
||||
res = 0
|
||||
for line in data.splitlines():
|
||||
vc = 0
|
||||
for c in line:
|
||||
if c in "aoeui":
|
||||
vc += 1
|
||||
if vc < 3:
|
||||
continue
|
||||
|
||||
prev = None
|
||||
for c in line:
|
||||
if c == prev:
|
||||
break
|
||||
prev = c
|
||||
else:
|
||||
continue
|
||||
def is_nice(line):
|
||||
# It contains at least three vowels (aeiou only), like aei, xazegov, or aeiouaeiouaeiou.
|
||||
vc = 0
|
||||
for v in "aeiou":
|
||||
vc += line.count(v)
|
||||
if vc < 3:
|
||||
return False
|
||||
|
||||
contains = False
|
||||
ss = ["ab", "cd", "pq", "xy"]
|
||||
for s in ss:
|
||||
if s in line:
|
||||
contains = True
|
||||
# It contains at least one letter that appears twice in a row, like xx,
|
||||
# abcdde (dd), or aabbccdd (aa, bb, cc, or dd).
|
||||
for i in range(len(line) - 1):
|
||||
if line[i] == line[i + 1]:
|
||||
break
|
||||
else:
|
||||
return False
|
||||
|
||||
if contains:
|
||||
continue
|
||||
res += 1
|
||||
print(res)
|
||||
else:
|
||||
res = 0
|
||||
for line in data.splitlines():
|
||||
pairs = {}
|
||||
for i in range(0, len(line) - 1):
|
||||
p = line[i:i+2]
|
||||
if p in pairs and i > pairs[p] + 1:
|
||||
break
|
||||
if not p in pairs:
|
||||
pairs[p] = i
|
||||
else:
|
||||
continue
|
||||
# It does not contain the strings ab, cd, pq, or xy, even if they are
|
||||
# part of one of the other requirements.
|
||||
for i in range(len(line) - 1):
|
||||
cc = "".join(line[i : i + 2])
|
||||
if cc in ["ab", "cd", "pq", "xy"]:
|
||||
return False
|
||||
return True
|
||||
|
||||
for i in range(0, len(line) - 2):
|
||||
if line[i] == line[i + 2]:
|
||||
break
|
||||
else:
|
||||
continue
|
||||
res += 1
|
||||
print(res)
|
||||
|
||||
def is_nice_2(line):
|
||||
good = False
|
||||
for i in range(len(line) - 1):
|
||||
cc = "".join(line[i : i + 2])
|
||||
for j in range(i + 2, len(line) - 1):
|
||||
dd = "".join(line[j : j + 2])
|
||||
if cc == dd:
|
||||
good = True
|
||||
if not good:
|
||||
return False
|
||||
|
||||
for i in range(len(line) - 2):
|
||||
cc = "".join(line[i : i + 3])
|
||||
if cc[0] == cc[2]:
|
||||
break
|
||||
else:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
t = sum(1 if is_nice(line) else 0 for line in data.splitlines())
|
||||
print(t)
|
||||
|
||||
|
||||
t = sum(1 if is_nice_2(line) else 0 for line in data.splitlines())
|
||||
print(t)
|
||||
|
||||
46
2015/d6.py
46
2015/d6.py
@@ -1,33 +1,29 @@
|
||||
from lib import *
|
||||
from lib import get_data, ints
|
||||
from collections import defaultdict as DD
|
||||
|
||||
data = open(0).read()
|
||||
|
||||
part_2 = True
|
||||
lights = [[0 for _ in range(1000)] for _ in range(1000)]
|
||||
data = get_data(__file__)
|
||||
|
||||
ons = set()
|
||||
ons2 = DD(int)
|
||||
for line in data.splitlines():
|
||||
x1, y1, x2, y2 = str_to_ints(line)
|
||||
x1, y1, x2, y2 = ints(line)
|
||||
for x in range(x1, x2 + 1):
|
||||
for y in range(y1, y2 + 1):
|
||||
if part_2:
|
||||
if "on" in line:
|
||||
lights[x][y] += 1
|
||||
elif "off" in line:
|
||||
lights[x][y] = max(0, lights[x][y] - 1)
|
||||
if "off" in line:
|
||||
if (x, y) in ons:
|
||||
ons.remove((x, y))
|
||||
ons2[(x, y)] = max(ons2[(x, y)] - 1, 0)
|
||||
elif "on" in line:
|
||||
ons.add((x, y))
|
||||
ons2[(x, y)] += 1
|
||||
elif "toggle" in line:
|
||||
if (x, y) in ons:
|
||||
ons.remove((x, y))
|
||||
else:
|
||||
lights[x][y] += 2
|
||||
ons.add((x, y))
|
||||
ons2[(x, y)] += 2
|
||||
else:
|
||||
if "on" in line:
|
||||
lights[x][y] = 1
|
||||
elif "off" in line:
|
||||
lights[x][y] = 0
|
||||
else:
|
||||
if lights[x][y] == 1:
|
||||
lights[x][y] = 0
|
||||
else:
|
||||
lights[x][y] = 1
|
||||
assert False
|
||||
|
||||
res = 0
|
||||
for row in lights:
|
||||
res += sum(row)
|
||||
print(res)
|
||||
print(len(ons))
|
||||
print(sum(ons2.values()))
|
||||
|
||||
134
2015/d7.py
134
2015/d7.py
@@ -1,90 +1,54 @@
|
||||
from lib import *
|
||||
from lib import get_data
|
||||
|
||||
data = open(0).read()
|
||||
data = get_data(__file__)
|
||||
|
||||
part_2 = False
|
||||
if part_2:
|
||||
gates = {"b": 16076}
|
||||
else:
|
||||
gates = {}
|
||||
|
||||
while "a" not in gates:
|
||||
for line in data.splitlines():
|
||||
lhs, rhs = line.split(" -> ")
|
||||
if part_2 and rhs == "b":
|
||||
continue
|
||||
lhs = lhs.strip()
|
||||
if "NOT" in lhs:
|
||||
op, op1 = lhs.split(" ")
|
||||
assert op == "NOT"
|
||||
try:
|
||||
op1 = int(op1)
|
||||
gates[rhs] = ~op1 & 0xffff
|
||||
except ValueError:
|
||||
if op1 in gates and isinstance(gates[op1], int):
|
||||
gates[rhs] = ~gates[op1] & 0xffff
|
||||
elif "OR" in lhs:
|
||||
op1, op, op2 = lhs.split(" ")
|
||||
assert op == "OR"
|
||||
try:
|
||||
op1 = int(op1)
|
||||
except ValueError:
|
||||
if op1 in gates and isinstance(gates[op1], int):
|
||||
op1 = gates[op1]
|
||||
try:
|
||||
op2 = int(op2)
|
||||
except ValueError:
|
||||
if op2 in gates and isinstance(gates[op2], int):
|
||||
op2 = gates[op2]
|
||||
|
||||
if not (isinstance(op1, int) and isinstance(op2, int)):
|
||||
continue
|
||||
gates[rhs] = (op1 | op2)
|
||||
elif "AND" in lhs:
|
||||
op1, op, op2 = lhs.split(" ")
|
||||
assert op == "AND"
|
||||
try:
|
||||
op1 = int(op1)
|
||||
except ValueError:
|
||||
if op1 in gates and isinstance(gates[op1], int):
|
||||
op1 = gates[op1]
|
||||
try:
|
||||
op2 = int(op2)
|
||||
except ValueError:
|
||||
if op2 in gates and isinstance(gates[op2], int):
|
||||
op2 = gates[op2]
|
||||
|
||||
if not (isinstance(op1, int) and isinstance(op2, int)):
|
||||
continue
|
||||
gates[rhs] = (op1 & op2)
|
||||
elif "LSHIFT" in lhs:
|
||||
op1, op, op2 = lhs.split(" ")
|
||||
op2 = int(op2)
|
||||
try:
|
||||
op1 = int(op1)
|
||||
except ValueError:
|
||||
if op1 in gates:
|
||||
op1 = gates[op1]
|
||||
else:
|
||||
continue
|
||||
gates[rhs] = (op1 << op2) & 0xffff
|
||||
elif "RSHIFT" in lhs:
|
||||
op1, op, op2 = lhs.split(" ")
|
||||
op2 = int(op2)
|
||||
try:
|
||||
op1 = int(op1)
|
||||
except ValueError:
|
||||
if op1 in gates:
|
||||
op1 = gates[op1]
|
||||
else:
|
||||
continue
|
||||
gates[rhs] = (op1 >> op2) & 0xffff
|
||||
def run(wires={}):
|
||||
def get(a):
|
||||
try:
|
||||
return int(a)
|
||||
except ValueError:
|
||||
pass
|
||||
if a in wires:
|
||||
return wires[a]
|
||||
else:
|
||||
try:
|
||||
lhs = int(lhs)
|
||||
gates[rhs] = lhs
|
||||
except ValueError:
|
||||
if lhs in gates:
|
||||
gates[rhs] = gates[lhs]
|
||||
return None
|
||||
|
||||
print(gates["a"])
|
||||
while "a" not in wires:
|
||||
for line in data.splitlines():
|
||||
lhs, rhs = line.split(" -> ")
|
||||
if rhs in wires:
|
||||
continue
|
||||
match lhs.split():
|
||||
case [a, "AND", b]:
|
||||
a, b = get(a), get(b)
|
||||
if a is not None and b is not None:
|
||||
wires[rhs] = a & b
|
||||
case [a, "OR", b]:
|
||||
a, b = get(a), get(b)
|
||||
if a is not None and b is not None:
|
||||
wires[rhs] = a | b
|
||||
case [a, "LSHIFT", b]:
|
||||
a, b = get(a), get(b)
|
||||
if a is not None and b is not None:
|
||||
wires[rhs] = a << b
|
||||
case [a, "RSHIFT", b]:
|
||||
a, b = get(a), get(b)
|
||||
if a is not None and b is not None:
|
||||
wires[rhs] = a >> b
|
||||
case ["NOT", a]:
|
||||
a = get(a)
|
||||
if a is not None:
|
||||
wires[rhs] = ~a & 0xFFFF
|
||||
case [a]:
|
||||
a = get(a)
|
||||
if a is not None:
|
||||
wires[rhs] = a
|
||||
return wires
|
||||
|
||||
|
||||
a = run()["a"]
|
||||
print(a)
|
||||
|
||||
a2 = run(wires={"b": a})["a"]
|
||||
print(a2)
|
||||
|
||||
45
2015/d8.py
45
2015/d8.py
@@ -1,26 +1,25 @@
|
||||
from lib import *
|
||||
from lib import get_data
|
||||
import re
|
||||
|
||||
data = open(0).read()
|
||||
part_1 = False
|
||||
data = get_data(__file__)
|
||||
|
||||
if part_1:
|
||||
r1 = re.compile(r"\\x[0-9a-f][0-9a-f]")
|
||||
r2 = re.compile(r"\\\\")
|
||||
r3 = re.compile(r"\\\"")
|
||||
r1 = re.compile(r"\\x[0-9a-f][0-9a-f]")
|
||||
r2 = re.compile(r"\\\\")
|
||||
r3 = re.compile(r"\\\"")
|
||||
|
||||
enc, mem = 0, 0
|
||||
for line in data.splitlines():
|
||||
mem += len(line)
|
||||
line = r1.sub("^", line)
|
||||
line = r2.sub("^", line)
|
||||
line = r3.sub("^", line)
|
||||
enc += len(line) - 2
|
||||
print(mem - enc)
|
||||
else:
|
||||
ori, enc = 0, 0
|
||||
for line in data.splitlines():
|
||||
ori += len(line)
|
||||
line = line.replace("\\", "\\\\")
|
||||
line = line.replace("\"", "\\\"")
|
||||
enc += len(line) + 2
|
||||
print(enc - ori)
|
||||
enc, mem = 0, 0
|
||||
for line in data.splitlines():
|
||||
mem += len(line)
|
||||
line = r1.sub("^", line)
|
||||
line = r2.sub("^", line)
|
||||
line = r3.sub("^", line)
|
||||
enc += len(line) - 2
|
||||
print(mem - enc)
|
||||
|
||||
ori, enc = 0, 0
|
||||
for line in data.splitlines():
|
||||
ori += len(line)
|
||||
line = line.replace("\\", "\\\\")
|
||||
line = line.replace('"', '\\"')
|
||||
enc += len(line) + 2
|
||||
print(enc - ori)
|
||||
|
||||
49
2015/d9.py
49
2015/d9.py
@@ -1,42 +1,27 @@
|
||||
from lib import *
|
||||
from typing import Iterator
|
||||
from lib import get_data
|
||||
from itertools import permutations
|
||||
|
||||
# Just for fun instead of using itertools.permutations.
|
||||
def permutations(xs) -> Iterator:
|
||||
assert len(xs) > 0
|
||||
if len(xs) == 1:
|
||||
yield xs
|
||||
else:
|
||||
x = xs.pop()
|
||||
for p in permutations(xs):
|
||||
for i in range(len(p) + 1):
|
||||
pn = list(p)
|
||||
pn.insert(i, x)
|
||||
yield pn
|
||||
|
||||
part_1 = False
|
||||
data = open(0).read()
|
||||
data = get_data(__file__)
|
||||
|
||||
dists = {}
|
||||
nodes = set()
|
||||
for line in data.splitlines():
|
||||
path, dist = line.split(" = ")
|
||||
dist = int(dist)
|
||||
a, b = path.split(" to ")
|
||||
a, _, b, _, d = line.split()
|
||||
nodes.add(a)
|
||||
nodes.add(b)
|
||||
dist = int(d)
|
||||
dists[(a, b)] = dist
|
||||
dists[(b, a)] = dist
|
||||
|
||||
if part_1:
|
||||
mdist = 10**12
|
||||
for route in permutations(nodes):
|
||||
dist = sum([dists[(route[i], route[i + 1])] for i in range(len(route) - 1)])
|
||||
mdist = min(dist, mdist)
|
||||
print(mdist)
|
||||
else:
|
||||
mdist = 0
|
||||
for route in permutations(nodes):
|
||||
dist = sum([dists[(route[i], route[i + 1])] for i in range(len(route) - 1)])
|
||||
mdist = max(dist, mdist)
|
||||
print(mdist)
|
||||
d_min = 10**12
|
||||
d_max = 0
|
||||
for p in permutations(list(nodes)):
|
||||
d = 0
|
||||
for i in range(len(p) - 1):
|
||||
a, b = p[i], p[i + 1]
|
||||
d += dists[(a, b)]
|
||||
d_min = min(d, d_min)
|
||||
d_max = max(d, d_max)
|
||||
|
||||
print(d_min)
|
||||
print(d_max)
|
||||
|
||||
224
2018/d20.py
224
2018/d20.py
@@ -1,11 +1,6 @@
|
||||
from lib import get_data, add2
|
||||
from collections import defaultdict
|
||||
|
||||
data = "^WNE$"
|
||||
data = "^ENWWW(NEEE|SSE(EE|N))$"
|
||||
data = "^ESSWWN(E|NNENN(EESS(WNSE|)SSS|WWWSSSSE(SW|NNNE)))$"
|
||||
data = "^WSSEESWWWNW(S|NENNEEEENN(ESSSSW(NWSW|SSEN)|WSWWN(E|WWS(E|SS))))$"
|
||||
|
||||
DIRS = {
|
||||
"N": (-1, 0),
|
||||
"E": (0, 1),
|
||||
@@ -13,148 +8,87 @@ DIRS = {
|
||||
"W": (0, -1),
|
||||
}
|
||||
|
||||
data = get_data(__file__).strip()
|
||||
g = defaultdict(set)
|
||||
seen = set()
|
||||
stack: list[tuple[tuple, int, list]] = [((0, 0), 0, [])]
|
||||
while len(stack) > 0:
|
||||
pos, i, i_outs = stack.pop()
|
||||
|
||||
def part_1(data):
|
||||
g = defaultdict(set)
|
||||
c = (pos, i, tuple(i_outs))
|
||||
if c in seen:
|
||||
continue
|
||||
else:
|
||||
seen.add(c)
|
||||
|
||||
stack: list[tuple[tuple, int, list]] = [((0, 0), 0, [])]
|
||||
while len(stack) > 0:
|
||||
pos, i, i_outs = stack.pop()
|
||||
assert i is not None
|
||||
while i < len(data):
|
||||
c = data[i]
|
||||
if c in DIRS.keys():
|
||||
npos = add2(pos, DIRS[c])
|
||||
g[pos].add(npos)
|
||||
g[npos].add(pos)
|
||||
pos = npos
|
||||
i += 1
|
||||
elif c == "(":
|
||||
to_continue = [i + 1]
|
||||
open_count = 0
|
||||
j_out = None
|
||||
for j in range(i + 1, len(data)):
|
||||
c = data[j]
|
||||
if c == "|" and open_count == 0:
|
||||
to_continue.append(j + 1)
|
||||
elif c == "(":
|
||||
open_count += 1
|
||||
elif c == ")" and open_count != 0:
|
||||
open_count -= 1
|
||||
elif c == ")" and open_count == 0:
|
||||
j_out = j
|
||||
break
|
||||
assert j_out is not None
|
||||
assert i is not None
|
||||
while i < len(data):
|
||||
c = data[i]
|
||||
if c in DIRS.keys():
|
||||
npos = add2(pos, DIRS[c])
|
||||
g[pos].add(npos)
|
||||
g[npos].add(pos)
|
||||
pos = npos
|
||||
i += 1
|
||||
elif c == "(":
|
||||
to_continue = [i + 1]
|
||||
open_count = 0
|
||||
j_out = None
|
||||
for j in range(i + 1, len(data)):
|
||||
c = data[j]
|
||||
if c == "|" and open_count == 0:
|
||||
to_continue.append(j + 1)
|
||||
elif c == "(":
|
||||
open_count += 1
|
||||
elif c == ")" and open_count != 0:
|
||||
open_count -= 1
|
||||
elif c == ")" and open_count == 0:
|
||||
j_out = j
|
||||
break
|
||||
assert j_out is not None
|
||||
|
||||
for new_i in to_continue:
|
||||
new_i_outs = list(i_outs)
|
||||
new_i_outs.append(j_out)
|
||||
stack.append((pos, new_i, new_i_outs))
|
||||
break
|
||||
elif c == "$":
|
||||
break
|
||||
elif c == ")" and len(i_outs) == 0:
|
||||
assert False, "Encountered | without i_out"
|
||||
elif c == ")":
|
||||
i_new = i_outs.pop()
|
||||
assert i == i_new
|
||||
i += 1
|
||||
elif c == "^":
|
||||
i += 1
|
||||
elif c == "|" and len(i_outs) == 0:
|
||||
assert False, "Encountered | without i_out"
|
||||
elif c == "|":
|
||||
i = i_outs.pop()
|
||||
i += 1
|
||||
else:
|
||||
assert False
|
||||
for new_i in to_continue:
|
||||
new_i_outs = list(i_outs)
|
||||
new_i_outs.append(j_out)
|
||||
stack.append((pos, new_i, new_i_outs))
|
||||
break
|
||||
elif c == "$":
|
||||
break
|
||||
elif c == ")" and len(i_outs) == 0:
|
||||
assert False, "Encountered | without i_out"
|
||||
elif c == ")":
|
||||
i_new = i_outs.pop()
|
||||
assert i == i_new
|
||||
i += 1
|
||||
elif c == "^":
|
||||
i += 1
|
||||
elif c == "|" and len(i_outs) == 0:
|
||||
assert False, "Encountered | without i_out"
|
||||
elif c == "|":
|
||||
i = i_outs.pop()
|
||||
i += 1
|
||||
else:
|
||||
assert False
|
||||
|
||||
seen = set()
|
||||
dists = {}
|
||||
xs = [(0, 0)]
|
||||
steps = 0
|
||||
over_thousand = set()
|
||||
while len(xs) > 0:
|
||||
nxs = []
|
||||
for x in xs:
|
||||
if x in seen:
|
||||
continue
|
||||
if steps >= 1000:
|
||||
over_thousand.add(x)
|
||||
seen.add(x)
|
||||
dists[x] = steps
|
||||
for nb in g[x]:
|
||||
if not nb in seen:
|
||||
nxs.append(nb)
|
||||
xs = nxs
|
||||
steps += 1
|
||||
|
||||
# def parse(i):
|
||||
# max_len = 0
|
||||
# cur_len = 0
|
||||
# while i < len(data):
|
||||
# c = data[i]
|
||||
# if c in DIRS.keys():
|
||||
# i += 1
|
||||
# cur_len += 1
|
||||
# elif c == "(":
|
||||
# sub_len, i = parse(i + 1)
|
||||
# cur_len += sub_len
|
||||
# elif c == "|":
|
||||
# max_len = max(max_len, cur_len)
|
||||
# cur_len = 0
|
||||
# i += 1
|
||||
# elif c == ")":
|
||||
# all_max = max(max_len, cur_len)
|
||||
# return all_max, i + 1
|
||||
# elif c == "$":
|
||||
# max_len = max(max_len, cur_len)
|
||||
# i += 1
|
||||
# else:
|
||||
# print(c)
|
||||
# assert False
|
||||
# return max_len, i
|
||||
#
|
||||
# print(parse(0)[0])
|
||||
|
||||
# g = defaultdict(set)
|
||||
# def parse(xs, i):
|
||||
# xs_orig = xs.copy()
|
||||
# xs_done = []
|
||||
#
|
||||
# while i < len(data):
|
||||
# c = data[i]
|
||||
# if c in DIRS.keys():
|
||||
# for xi in range(len(xs)):
|
||||
# pos = xs[xi]
|
||||
# npos = add2(pos, DIRS[c])
|
||||
# g[pos].add(npos)
|
||||
# g[npos].add(pos)
|
||||
# xs[xi] = npos
|
||||
# i += 1
|
||||
# elif c == "(":
|
||||
# xs, i = parse(xs, i + 1)
|
||||
# elif c == "|":
|
||||
# xs_done += xs
|
||||
# xs = xs_orig
|
||||
# i += 1
|
||||
# elif c == ")":
|
||||
# xs_done += xs
|
||||
# return xs_done, i + 1
|
||||
# elif c == "$":
|
||||
# xs_done += xs
|
||||
# i += 1
|
||||
# else:
|
||||
# assert False
|
||||
# return xs_done, i
|
||||
#
|
||||
# parse([(0, 0)], 0)
|
||||
|
||||
seen = set()
|
||||
dists = {}
|
||||
xs = [(0, 0)]
|
||||
steps = 0
|
||||
while len(xs) > 0:
|
||||
nxs = []
|
||||
for x in xs:
|
||||
if x in seen:
|
||||
continue
|
||||
seen.add(x)
|
||||
dists[x] = steps
|
||||
for nb in g[x]:
|
||||
if not nb in seen:
|
||||
nxs.append(nb)
|
||||
xs = nxs
|
||||
steps += 1
|
||||
|
||||
print(max(dists.values()))
|
||||
|
||||
|
||||
def main():
|
||||
# data = get_data(__file__).strip()
|
||||
part_1(data)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
print(max(dists.values()))
|
||||
print(len(over_thousand))
|
||||
|
||||
179
2018/d24.py
Normal file
179
2018/d24.py
Normal 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
38
2018/d25.py
Normal 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))
|
||||
115
2019/d22.py
115
2019/d22.py
@@ -4,16 +4,17 @@ from math import gcd
|
||||
data = get_data(__file__)
|
||||
deck = list(range(10007))
|
||||
|
||||
# part 1
|
||||
for line in data.splitlines():
|
||||
if "new stack" in line:
|
||||
deck = list(reversed(deck))
|
||||
elif "cut" in line:
|
||||
n, = str_to_ints(line)
|
||||
(n,) = str_to_ints(line)
|
||||
deck = deck[n:] + deck[:n]
|
||||
elif "increment" in line:
|
||||
new_deck = [-1] * len(deck)
|
||||
pos = 0
|
||||
n, = str_to_ints(line)
|
||||
(n,) = str_to_ints(line)
|
||||
deck = list(reversed(deck))
|
||||
while deck:
|
||||
new_deck[pos] = deck.pop()
|
||||
@@ -24,30 +25,94 @@ for line in data.splitlines():
|
||||
|
||||
print(deck.index(2019))
|
||||
|
||||
# idea: just follow the index of the target card
|
||||
len = 119315717514047
|
||||
len = 10007
|
||||
index = 2019
|
||||
orig_index = 2019
|
||||
|
||||
# figure out how to reverse...
|
||||
index = orig_index
|
||||
for line in data.splitlines():
|
||||
if "new stack" in line:
|
||||
new_index = len - (index + 1)
|
||||
rev_index = -(new_index - len + 1)
|
||||
assert rev_index == index
|
||||
index = new_index
|
||||
elif "cut" in line:
|
||||
(cut,) = str_to_ints(line)
|
||||
cut = (len + cut) % len
|
||||
if index >= cut:
|
||||
new_index = index - cut
|
||||
else:
|
||||
new_index = (len - cut) + index
|
||||
rev_index = (new_index + cut) % len
|
||||
assert rev_index == index
|
||||
index = new_index
|
||||
# calculate index from new_index and store in rev_index
|
||||
elif "increment" in line:
|
||||
(n,) = str_to_ints(line)
|
||||
assert gcd(n, len) == 1
|
||||
new_index = (n * index) % len
|
||||
m = mod_inverse(n, len)
|
||||
rev_index = (new_index * m) % len
|
||||
assert rev_index == index
|
||||
index = new_index
|
||||
|
||||
for i in range(101741582076661):
|
||||
for line in data.splitlines():
|
||||
if "new stack" in line:
|
||||
index = len - (index + 1)
|
||||
elif "cut" in line:
|
||||
cut, = str_to_ints(line)
|
||||
cut = (len + cut) % len
|
||||
if index >= cut:
|
||||
index = index - cut
|
||||
else:
|
||||
index = (len - cut) + index
|
||||
elif "increment" in line:
|
||||
n, = str_to_ints(line)
|
||||
assert gcd(n, len) == 1
|
||||
new_index = (n * index) % len
|
||||
m = mod_inverse(n, len)
|
||||
assert (new_index * m) % len == index
|
||||
index = new_index
|
||||
break
|
||||
assert index == deck.index(2019)
|
||||
|
||||
print(index)
|
||||
# check that reverse approach works
|
||||
for line in reversed(data.splitlines()):
|
||||
if "new stack" in line:
|
||||
index = -(index - len + 1)
|
||||
elif "cut" in line:
|
||||
(cut,) = str_to_ints(line)
|
||||
cut = (len + cut) % len
|
||||
index = (index + cut) % len
|
||||
elif "increment" in line:
|
||||
(n,) = str_to_ints(line)
|
||||
assert gcd(n, len) == 1
|
||||
m = mod_inverse(n, len)
|
||||
index = (index * m) % len
|
||||
assert index == orig_index
|
||||
|
||||
lines = list(reversed(data.splitlines()))
|
||||
|
||||
# new length of deck
|
||||
len = 119315717514047
|
||||
|
||||
# get expression for one loop using sympy
|
||||
from sympy import symbols, simplify
|
||||
|
||||
index = symbols("index")
|
||||
expr = index
|
||||
for line in lines:
|
||||
if "new stack" in line:
|
||||
# index = -(index - len + 1)
|
||||
expr = -(expr - len + 1)
|
||||
elif "cut" in line:
|
||||
(cut,) = str_to_ints(line)
|
||||
cut = (len + cut) % len
|
||||
# index = (index + cut) % len
|
||||
expr = expr + cut
|
||||
elif "increment" in line:
|
||||
(n,) = str_to_ints(line)
|
||||
assert gcd(n, len) == 1
|
||||
m = mod_inverse(n, len)
|
||||
# index = (index * m) % len
|
||||
expr = expr * m
|
||||
|
||||
# we can see that expression is in the form (a - b * i) % m
|
||||
expr = simplify(expr % len)
|
||||
coeff_dict = expr.args[0].as_coefficients_dict()
|
||||
a = coeff_dict[1]
|
||||
b = -coeff_dict[index]
|
||||
|
||||
# math
|
||||
n_shuffles = 101741582076661
|
||||
r0 = 2020
|
||||
m = len
|
||||
p = (-b) % m
|
||||
p_t = pow(p, n_shuffles, m)
|
||||
inv_b1 = mod_inverse(b + 1, m)
|
||||
term1 = (p_t * r0) % m
|
||||
term2 = (a * (1 - p_t) * inv_b1) % m
|
||||
r_t = (term1 + term2) % m
|
||||
print(r_t)
|
||||
|
||||
47
2019/d23.py
Normal file
47
2019/d23.py
Normal 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
107
2019/d24.py
Normal 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
160
2019/d25.py
Normal file
@@ -0,0 +1,160 @@
|
||||
from lib import get_data, ints
|
||||
from d9 import Amp
|
||||
from itertools import combinations
|
||||
|
||||
data = get_data(__file__)
|
||||
xs = ints(data)
|
||||
|
||||
commands = [
|
||||
"north",
|
||||
"take candy cane",
|
||||
"west",
|
||||
"south",
|
||||
"south",
|
||||
"take fuel cell",
|
||||
"south",
|
||||
"take manifold",
|
||||
"north",
|
||||
"north",
|
||||
"west",
|
||||
"take mutex",
|
||||
"north",
|
||||
"south",
|
||||
"south",
|
||||
"south",
|
||||
"take coin",
|
||||
"south",
|
||||
"north",
|
||||
"east",
|
||||
"take cake",
|
||||
"west",
|
||||
"east",
|
||||
"north",
|
||||
"south",
|
||||
"west",
|
||||
"east",
|
||||
"east",
|
||||
"north",
|
||||
"south",
|
||||
"west",
|
||||
"north",
|
||||
"west",
|
||||
"east",
|
||||
"west",
|
||||
"south",
|
||||
"west",
|
||||
"north",
|
||||
"north",
|
||||
"south",
|
||||
"west",
|
||||
"north",
|
||||
"east",
|
||||
"south",
|
||||
"west",
|
||||
"east",
|
||||
"west",
|
||||
"south",
|
||||
"west",
|
||||
"take dehydrated water",
|
||||
"west",
|
||||
"east",
|
||||
"west",
|
||||
"south",
|
||||
"take prime number",
|
||||
"south",
|
||||
"north",
|
||||
"east",
|
||||
"east",
|
||||
"west",
|
||||
"north",
|
||||
"east",
|
||||
"east",
|
||||
"north",
|
||||
"south",
|
||||
"west",
|
||||
"north",
|
||||
"west",
|
||||
"south",
|
||||
"inv",
|
||||
"west",
|
||||
]
|
||||
|
||||
items = [
|
||||
"cake",
|
||||
"prime number",
|
||||
"mutex",
|
||||
"dehydrated water",
|
||||
"coin",
|
||||
"manifold",
|
||||
"candy cane",
|
||||
"fuel cell",
|
||||
]
|
||||
|
||||
|
||||
def run_command(a, s):
|
||||
for c in s:
|
||||
a.feed(ord(c))
|
||||
a.feed(10)
|
||||
a.go()
|
||||
|
||||
|
||||
a = Amp(xs)
|
||||
for c in commands:
|
||||
a.go()
|
||||
while a.outputs:
|
||||
a.go()
|
||||
a.pop()
|
||||
# print(chr(a.pop()), end="")
|
||||
run_command(a, c)
|
||||
a.go()
|
||||
|
||||
|
||||
def try_all(a):
|
||||
subsets = [list(combinations(items, r)) for r in range(1, len(items) + 1)]
|
||||
subsets = [item for sublist in subsets for item in sublist]
|
||||
for subset in subsets:
|
||||
for item in items:
|
||||
c = f"drop {item}"
|
||||
run_command(a, c)
|
||||
a.go()
|
||||
while a.outputs:
|
||||
a.pop()
|
||||
a.go()
|
||||
|
||||
for item in subset:
|
||||
c = f"take {item}"
|
||||
run_command(a, c)
|
||||
a.go()
|
||||
while a.outputs:
|
||||
a.pop()
|
||||
a.go()
|
||||
|
||||
run_command(a, "west")
|
||||
|
||||
output_str = ""
|
||||
while a.outputs:
|
||||
a.go()
|
||||
output_str += chr(a.pop())
|
||||
if "lighter" in output_str:
|
||||
pass
|
||||
elif "heavier" in output_str:
|
||||
pass
|
||||
else:
|
||||
(password,) = ints(output_str)
|
||||
print(password)
|
||||
exit()
|
||||
|
||||
|
||||
while True:
|
||||
a.go()
|
||||
|
||||
output_str = ""
|
||||
while a.outputs:
|
||||
a.go()
|
||||
output_str += chr(a.pop())
|
||||
|
||||
try_all(a)
|
||||
|
||||
if a.input_required:
|
||||
c = input(">")
|
||||
run_command(a, c)
|
||||
20
2021/d1.py
Normal file
20
2021/d1.py
Normal 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
63
2021/d10.py
Normal 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
37
2021/d11.py
Normal 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
56
2021/d12.py
Normal 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
51
2021/d13.py
Normal 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
43
2021/d14.py
Normal 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
83
2021/d15.py
Normal 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
85
2021/d16.py
Normal 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
35
2021/d17.py
Normal 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
115
2021/d18.py
Normal file
@@ -0,0 +1,115 @@
|
||||
from lib import get_data
|
||||
from math import ceil, floor
|
||||
from itertools import combinations
|
||||
|
||||
data = """[[[0,[5,8]],[[1,7],[9,6]]],[[4,[1,2]],[[1,4],2]]]
|
||||
[[[5,[2,8]],4],[5,[[9,9],0]]]
|
||||
[6,[[[6,2],[5,6]],[[7,6],[4,7]]]]
|
||||
[[[6,[0,7]],[0,9]],[4,[9,[9,0]]]]
|
||||
[[[7,[6,4]],[3,[1,3]]],[[[5,5],1],9]]
|
||||
[[6,[[7,3],[3,2]]],[[[3,8],[5,7]],4]]
|
||||
[[[[5,4],[7,7]],8],[[8,3],8]]
|
||||
[[9,3],[[9,9],[6,[4,9]]]]
|
||||
[[2,[[7,7],7]],[[5,8],[[9,3],[0,2]]]]
|
||||
[[[[5,2],5],[8,[3,7]]],[[5,[7,5]],[4,4]]]"""
|
||||
data = get_data(__file__)
|
||||
|
||||
|
||||
def parse(line):
|
||||
xs = list(line)
|
||||
for i in range(len(xs)):
|
||||
try:
|
||||
xs[i] = int(xs[i])
|
||||
except ValueError:
|
||||
pass
|
||||
assert line == "".join(map(str, xs))
|
||||
return xs
|
||||
|
||||
|
||||
def reduce(xs):
|
||||
reduced, nesting = False, 0
|
||||
for i in range(len(xs)):
|
||||
c = xs[i]
|
||||
if c == "[" and nesting == 4 and not reduced:
|
||||
lhs, rhs = xs[i + 1], xs[i + 3]
|
||||
if not (type(lhs) is int and type(rhs) is int):
|
||||
continue
|
||||
xs = xs[:i] + [0] + xs[i + 5 :]
|
||||
|
||||
for j in range(i - 1, -1, -1):
|
||||
if type(xs[j]) is int:
|
||||
xs[j] += lhs
|
||||
break
|
||||
|
||||
for j in range(i + 1, len(xs)):
|
||||
if type(xs[j]) is int:
|
||||
xs[j] += rhs
|
||||
break
|
||||
reduced = True
|
||||
break
|
||||
elif c == "[":
|
||||
nesting += 1
|
||||
elif c == "]":
|
||||
nesting -= 1
|
||||
|
||||
if not reduced:
|
||||
for i in range(len(xs)):
|
||||
c = xs[i]
|
||||
if type(c) is int and c >= 10:
|
||||
p = [floor(c / 2), ceil(c / 2)]
|
||||
xs = xs[:i] + ["[", p[0], ",", p[1], "]"] + xs[i + 1 :]
|
||||
reduced = True
|
||||
break
|
||||
return xs
|
||||
|
||||
|
||||
def restore(xs):
|
||||
return "".join(map(str, xs))
|
||||
|
||||
|
||||
assert restore(reduce(parse("[[[[[9,8],1],2],3],4]"))) == "[[[[0,9],2],3],4]"
|
||||
assert restore(reduce(parse("[7,[6,[5,[4,[3,2]]]]]"))) == "[7,[6,[5,[7,0]]]]"
|
||||
assert (
|
||||
restore(reduce(parse("[[3,[2,[1,[7,3]]]],[6,[5,[4,[3,2]]]]]")))
|
||||
== "[[3,[2,[8,0]]],[9,[5,[4,[3,2]]]]]"
|
||||
)
|
||||
assert (
|
||||
restore(reduce(parse("[[3,[2,[8,0]]],[9,[5,[4,[3,2]]]]]")))
|
||||
== "[[3,[2,[8,0]]],[9,[5,[7,0]]]]"
|
||||
)
|
||||
|
||||
|
||||
def add(a, b):
|
||||
s = ["["] + a + [","] + b + ["]"]
|
||||
while True:
|
||||
ns = reduce(s)
|
||||
if s == ns:
|
||||
break
|
||||
s = ns
|
||||
return s
|
||||
|
||||
|
||||
a = "[[[[4,3],4],4],[7,[[8,4],9]]]"
|
||||
b = "[1,1]"
|
||||
assert restore(add(parse(a), parse(b))) == "[[[[0,7],4],[[7,8],[6,0]]],[8,1]]"
|
||||
|
||||
lines = list(data.splitlines())
|
||||
s = parse(lines[0].strip())
|
||||
for line in lines[1:]:
|
||||
s = add(s, parse(line.strip()))
|
||||
|
||||
|
||||
def get_mag(xs):
|
||||
if type(xs) is int:
|
||||
return xs
|
||||
return get_mag(xs[0]) * 3 + get_mag(xs[1]) * 2
|
||||
|
||||
|
||||
print(get_mag(eval(restore(s))))
|
||||
|
||||
lines = list(data.splitlines())
|
||||
m = 0
|
||||
for a, b in combinations(lines, 2):
|
||||
m = max(m, get_mag(eval(restore(add(parse(a), parse(b))))))
|
||||
m = max(m, get_mag(eval(restore(add(parse(b), parse(a))))))
|
||||
print(m)
|
||||
121
2021/d19.py
Normal file
121
2021/d19.py
Normal 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
24
2021/d2.py
Normal file
@@ -0,0 +1,24 @@
|
||||
from lib import get_data
|
||||
from lib import ints
|
||||
|
||||
data = get_data(__file__)
|
||||
|
||||
aim, h2, d2 = 0, 0, 0
|
||||
h, d = 0, 0
|
||||
for line in data.splitlines():
|
||||
(v,) = ints(line)
|
||||
if "forward" in line:
|
||||
h2 += v
|
||||
d2 += aim * v
|
||||
h += v
|
||||
elif "down" in line:
|
||||
aim += v
|
||||
d += v
|
||||
elif "up" in line:
|
||||
aim -= v
|
||||
d -= v
|
||||
else:
|
||||
assert False
|
||||
|
||||
print(h * d)
|
||||
print(h2 * d2)
|
||||
51
2021/d20.py
Normal file
51
2021/d20.py
Normal file
@@ -0,0 +1,51 @@
|
||||
from lib import get_data
|
||||
|
||||
data = get_data(__file__)
|
||||
|
||||
for rounds in [2, 50]:
|
||||
algo, img = data.strip().split("\n\n")
|
||||
algo = algo.replace("\n", "")
|
||||
|
||||
xs = set()
|
||||
img = img.splitlines()
|
||||
for row in range(len(img)):
|
||||
for col in range(len(img[0])):
|
||||
if img[row][col] == "#":
|
||||
xs.add((row, col))
|
||||
|
||||
for i in range(rounds):
|
||||
y_min = min(v[0] for v in xs) - 6
|
||||
y_max = max(v[0] for v in xs) + 6
|
||||
x_min = min(v[1] for v in xs) - 6
|
||||
x_max = max(v[1] for v in xs) + 6
|
||||
|
||||
nxs = set()
|
||||
for row in range(y_min, y_max + 1):
|
||||
for col in range(x_min, x_max + 1):
|
||||
bin = ""
|
||||
for ro in range(3):
|
||||
for co in range(3):
|
||||
if (row + ro, col + co) in xs:
|
||||
bin += "1"
|
||||
else:
|
||||
bin += "0"
|
||||
bin = int(bin, 2)
|
||||
if algo[bin] == "#":
|
||||
nxs.add((row, col))
|
||||
|
||||
xs = nxs
|
||||
y_min_trim = y_min + 7
|
||||
y_max_trim = y_max - 8
|
||||
x_min_trim = x_min + 7
|
||||
x_max_trim = x_max - 8
|
||||
|
||||
# special clean-up required because input creates pixels when there
|
||||
# are no other pixels around (0b0_0000_0000)
|
||||
if i % 2 == 1:
|
||||
xs = [
|
||||
(r, c)
|
||||
for (r, c) in xs
|
||||
if y_min_trim <= r <= y_max_trim and x_min_trim <= c <= x_max_trim
|
||||
]
|
||||
|
||||
print(len(xs))
|
||||
65
2021/d21.py
Normal file
65
2021/d21.py
Normal file
@@ -0,0 +1,65 @@
|
||||
from lib import get_data
|
||||
from lib import ints
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
FIELD = 10
|
||||
DICE = 100
|
||||
dice = -1
|
||||
|
||||
data = get_data(__file__)
|
||||
|
||||
_, p1, _, p2 = ints(data)
|
||||
pos = [p1 - 1, p2 - 1]
|
||||
scores = [0, 0]
|
||||
|
||||
dice_rolls = 0
|
||||
not_done = True
|
||||
while not_done:
|
||||
for p in range(len(scores)):
|
||||
dice_rolls += 3
|
||||
for _ in range(3):
|
||||
dice += 1 % DICE
|
||||
pos[p] = (pos[p] + (dice + 1)) % FIELD
|
||||
scores[p] += pos[p] + 1
|
||||
if scores[p] >= 1000:
|
||||
not_done = False
|
||||
break
|
||||
|
||||
r = min(scores) * dice_rolls
|
||||
print(r)
|
||||
|
||||
_, p1, _, p2 = ints(data)
|
||||
pos = (p1 - 1, p2 - 1)
|
||||
scores = (0, 0)
|
||||
unis = defaultdict(int)
|
||||
won = [0, 0]
|
||||
unis[(0, pos, scores)] += 1
|
||||
|
||||
while unis:
|
||||
nunis = defaultdict(int)
|
||||
for (turn, opos, scores), count in unis.items():
|
||||
other = (turn + 1) % 2
|
||||
poss = [opos[turn]]
|
||||
for _ in range(3):
|
||||
nposs = []
|
||||
for dice in range(1, 3 + 1):
|
||||
for pos in poss:
|
||||
npos = (pos + dice) % FIELD
|
||||
nposs.append(npos)
|
||||
poss = nposs
|
||||
|
||||
for npos_player in poss:
|
||||
score = scores[turn] + npos_player + 1
|
||||
if score >= 21:
|
||||
won[turn] += count
|
||||
else:
|
||||
nscores = list(scores)
|
||||
nscores[turn] = score
|
||||
npos = list(opos)
|
||||
npos[turn] = npos_player
|
||||
state = (other, tuple(npos), tuple(nscores))
|
||||
nunis[state] += count
|
||||
unis = nunis
|
||||
|
||||
print(max(won))
|
||||
92
2021/d22.py
Normal file
92
2021/d22.py
Normal file
@@ -0,0 +1,92 @@
|
||||
from lib import get_data
|
||||
from lib import ints
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
data = get_data(__file__)
|
||||
|
||||
limit = 50
|
||||
ps = set()
|
||||
for i, line in enumerate(data.splitlines()):
|
||||
x_min, x_max, y_min, y_max, z_min, z_max = ints(line)
|
||||
for x in range(max(x_min, -limit), min(x_max, limit) + 1):
|
||||
for y in range(max(y_min, -limit), min(y_max, limit) + 1):
|
||||
for z in range(max(z_min, -limit), min(z_max, limit) + 1):
|
||||
if line.startswith("on"):
|
||||
ps.add((x, y, z))
|
||||
elif line.startswith("off"):
|
||||
if (x, y, z) in ps:
|
||||
ps.remove((x, y, z))
|
||||
else:
|
||||
assert False
|
||||
|
||||
print(len(ps))
|
||||
|
||||
|
||||
@dataclass
|
||||
class Line:
|
||||
a: int
|
||||
b: int
|
||||
|
||||
def intersects(self, other: "Line") -> bool:
|
||||
return self.b > other.a and self.a < other.b
|
||||
|
||||
def intersection(self, other: "Line") -> Optional["Line"]:
|
||||
if not self.intersects(other):
|
||||
return None
|
||||
return Line(max(self.a, other.a), min(self.b, other.b))
|
||||
|
||||
def length(self) -> int:
|
||||
assert self.b > self.a
|
||||
return self.b - self.a + 1
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Cube:
|
||||
on: bool
|
||||
x: Line
|
||||
y: Line
|
||||
z: Line
|
||||
|
||||
@classmethod
|
||||
def from_line(cls, line: str) -> "Cube":
|
||||
xs = ints(line)
|
||||
assert len(xs) == 6
|
||||
lines = [Line(*xs[i:i+2]) for i in range(0, len(xs), 2)]
|
||||
on = True if line.startswith("on") else False
|
||||
return Cube(on, *lines)
|
||||
|
||||
def volume(self) -> int:
|
||||
return (self.x.length() * self.y.length() * self.z.length()) * (1 if self.on else -1)
|
||||
|
||||
def intersects(self, other: "Cube") -> bool:
|
||||
return (
|
||||
self.x.intersects(other.x)
|
||||
and self.y.intersects(other.y)
|
||||
and self.z.intersects(other.z)
|
||||
)
|
||||
|
||||
def intersection(self, other: "Cube") -> Optional["Cube"]:
|
||||
x = self.x.intersection(other.x)
|
||||
y = self.y.intersection(other.y)
|
||||
z = self.z.intersection(other.z)
|
||||
if x is None or y is None or z is None:
|
||||
return None
|
||||
return Cube(not self.on, x, y, z)
|
||||
|
||||
|
||||
# Inclusion/exclusion principle from set theory
|
||||
cubes = []
|
||||
for line in data.splitlines():
|
||||
cc = Cube.from_line(line)
|
||||
ncubes = []
|
||||
for i in range(len(cubes)):
|
||||
c = cubes[i]
|
||||
ic = c.intersection(cc)
|
||||
if ic is not None:
|
||||
cubes.append(ic)
|
||||
|
||||
if cc.on:
|
||||
cubes.append(cc)
|
||||
|
||||
print(sum(c.volume() for c in cubes))
|
||||
143
2021/d23.py
Normal file
143
2021/d23.py
Normal file
@@ -0,0 +1,143 @@
|
||||
import heapq
|
||||
from lib import get_data
|
||||
from lib import Grid2D
|
||||
from collections import deque
|
||||
from collections import defaultdict
|
||||
|
||||
CHAR_COSTS = {"A": 1, "B": 10, "C": 100, "D": 1000}
|
||||
HALL_SPOTS = set([(1, x) for x in [1, 2, 4, 6, 8, 10, 11]])
|
||||
|
||||
for n_amphs in [2, 4]:
|
||||
AMPH_SPOTS = {}
|
||||
for i, c in enumerate("ABCD"):
|
||||
spots = [(r, 3 + i * 2) for r in range(2, 2 + n_amphs)]
|
||||
AMPH_SPOTS[c] = tuple(spots)
|
||||
|
||||
data = get_data(__file__)
|
||||
lines = data.splitlines()
|
||||
if n_amphs == 4:
|
||||
lines.insert(-2, " #D#C#B#A# ")
|
||||
lines.insert(-2, " #D#B#A#C# ")
|
||||
lines[-2] = lines[-2] + " "
|
||||
lines[-1] = lines[-1] + " "
|
||||
data = "\n".join(lines)
|
||||
|
||||
g = Grid2D(data)
|
||||
GRID_EMPTY = g.clone()
|
||||
|
||||
def mdist(p1, p2):
|
||||
return abs(p1[0] - p2[0]) + abs(p1[1] - p2[1])
|
||||
|
||||
poss = []
|
||||
for c in "ABCD":
|
||||
for coord in g.find(c):
|
||||
poss.append((coord[0], coord[1], c, False))
|
||||
GRID_EMPTY[coord] = "."
|
||||
poss = tuple(poss)
|
||||
|
||||
def dist(n1, n2):
|
||||
"""cost from node to node"""
|
||||
if n1 == 0:
|
||||
return 0
|
||||
return n2[1] - n1[1]
|
||||
|
||||
def h(node):
|
||||
"""heuristic function (never overestimate)"""
|
||||
cost = 0
|
||||
for r, c, char, _ in node[0]:
|
||||
if (r, c) not in AMPH_SPOTS[char]:
|
||||
cost += mdist((r, c), AMPH_SPOTS[char][0]) * CHAR_COSTS[char]
|
||||
return cost
|
||||
|
||||
def is_goal(node):
|
||||
for r, c, char, _ in node[0]:
|
||||
if (r, c) in AMPH_SPOTS[char]:
|
||||
continue
|
||||
return False
|
||||
return True
|
||||
|
||||
def find_accessible_spots(pos: tuple[int, int], char, blocked):
|
||||
start = (pos, 0)
|
||||
visited = set()
|
||||
queue = deque([start])
|
||||
cost = CHAR_COSTS[char]
|
||||
|
||||
options = []
|
||||
while queue:
|
||||
pos, dist = queue.popleft()
|
||||
if pos in visited:
|
||||
continue
|
||||
visited.add(pos)
|
||||
|
||||
for neighbor in GRID_EMPTY.neighbors_ort(pos):
|
||||
if neighbor in blocked:
|
||||
continue
|
||||
if GRID_EMPTY[neighbor] != ".":
|
||||
continue
|
||||
if neighbor not in visited:
|
||||
new_state = (neighbor, dist + cost)
|
||||
queue.append(new_state)
|
||||
options.append(new_state)
|
||||
return options
|
||||
|
||||
def neighbors(node):
|
||||
nbs = []
|
||||
|
||||
blocked = []
|
||||
blocked_to_char = defaultdict(str)
|
||||
for r, c, char, _ in node[0]:
|
||||
blocked.append((r, c))
|
||||
blocked_to_char[(r, c)] = char
|
||||
|
||||
for i, (r, c, char, moved) in enumerate(node[0]):
|
||||
if moved and (r, c) in AMPH_SPOTS[char]:
|
||||
continue
|
||||
|
||||
for pos, dist in find_accessible_spots((r, c), char, blocked):
|
||||
# cannot move to hall spots twice
|
||||
if pos in HALL_SPOTS and moved:
|
||||
continue
|
||||
|
||||
# can only move to hall spot or amph spot (of the right type)
|
||||
if not (pos in HALL_SPOTS or pos in AMPH_SPOTS[char]):
|
||||
continue
|
||||
|
||||
# only move into amph spot if all lower spots are already occupied by the correct amph
|
||||
if pos in AMPH_SPOTS[char] and not all(
|
||||
blocked_to_char[AMPH_SPOTS[char][i]] == char
|
||||
for i in range(
|
||||
AMPH_SPOTS[char].index(pos) + 1, len(AMPH_SPOTS[char])
|
||||
)
|
||||
):
|
||||
continue
|
||||
|
||||
amphs = list(node[0])
|
||||
amphs[i] = (pos[0], pos[1], char, True)
|
||||
neighbor = (tuple(amphs), node[1] + dist)
|
||||
nbs.append(neighbor)
|
||||
return nbs
|
||||
|
||||
starts = [(poss, 0)]
|
||||
open_set = []
|
||||
g_score = {}
|
||||
cost = None
|
||||
|
||||
for start in starts:
|
||||
heapq.heappush(open_set, (h(start), start))
|
||||
g_score[start] = dist(0, start)
|
||||
|
||||
while open_set:
|
||||
current_f_score, current = heapq.heappop(open_set)
|
||||
if is_goal(current):
|
||||
assert current_f_score == g_score[current]
|
||||
cost = g_score[current]
|
||||
break
|
||||
|
||||
for neighbor in neighbors(current):
|
||||
tentative_g_score = g_score[current] + dist(current, neighbor)
|
||||
if neighbor not in g_score or tentative_g_score < g_score[neighbor]:
|
||||
g_score[neighbor] = tentative_g_score
|
||||
f_score = g_score[neighbor] + h(neighbor)
|
||||
heapq.heappush(open_set, (f_score, neighbor))
|
||||
|
||||
print(cost)
|
||||
45
2021/d24.py
Normal file
45
2021/d24.py
Normal file
@@ -0,0 +1,45 @@
|
||||
from lib import get_data
|
||||
|
||||
data = get_data(__file__)
|
||||
|
||||
|
||||
def run(number):
|
||||
inputs = list(map(int, str(number)))
|
||||
assert len(inputs) == 14
|
||||
|
||||
w, x, z = 0, 0, 0
|
||||
i = 0
|
||||
for i in range(len(inputs)):
|
||||
w = inputs[i]
|
||||
# print(f"{w=:3} {x=:3} {z=}")
|
||||
|
||||
to_add_x = [15, 15, 12, 13, -12, 10, -9, 14, 13, -14, -11, -2, -16, -14][i]
|
||||
to_div_z = [1, 1, 1, 1, 26, 1, 26, 1, 1, 26, 26, 26, 26, 26][i]
|
||||
to_add_z = [15, 10, 2, 16, 12, 11, 5, 16, 6, 15, 3, 12, 10, 13][i]
|
||||
|
||||
zz = z
|
||||
x = (z % 26) + to_add_x
|
||||
z = z // to_div_z
|
||||
|
||||
# print(f"{zz % 26=} + {to_add_x=} {x=}")
|
||||
|
||||
if x == w:
|
||||
# print(f"added = 0")
|
||||
pass
|
||||
else:
|
||||
z *= 26
|
||||
z += w + to_add_z
|
||||
# print(f"added {w=} + {to_add_z=} = {w + to_add_z}")
|
||||
# print()
|
||||
|
||||
# print(f"\nEND:\n{w=} {x=} {z=}", "\n" * 15)
|
||||
return z
|
||||
|
||||
|
||||
largest_model_nr = 89959794919939
|
||||
assert run(largest_model_nr) == 0
|
||||
print(largest_model_nr)
|
||||
|
||||
lowest_model_nr = 17115131916112
|
||||
assert run(lowest_model_nr) == 0
|
||||
print(lowest_model_nr)
|
||||
29
2021/d25.py
Normal file
29
2021/d25.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from lib import get_data
|
||||
from lib import Grid2D
|
||||
|
||||
data = get_data(__file__)
|
||||
|
||||
g = Grid2D(data)
|
||||
seen = set()
|
||||
for i in range(10**9):
|
||||
if g.hash() in seen:
|
||||
print(i)
|
||||
break
|
||||
|
||||
seen.add(g.hash())
|
||||
blocked = set(g.find(">v"))
|
||||
for p in g.find(">"):
|
||||
np = (p[0], (p[1] + 1) % g.n_cols)
|
||||
if np in blocked:
|
||||
continue
|
||||
g[p] = "."
|
||||
g[np] = ">"
|
||||
|
||||
blocked = set(g.find(">v"))
|
||||
for p in g.find("v"):
|
||||
np = ((p[0] + 1) % g.n_rows , p[1])
|
||||
if np in blocked:
|
||||
continue
|
||||
g[p] = "."
|
||||
g[np] = "v"
|
||||
|
||||
56
2021/d3.py
Normal file
56
2021/d3.py
Normal file
@@ -0,0 +1,56 @@
|
||||
from lib import get_data
|
||||
from lib import ints
|
||||
from collections import defaultdict
|
||||
|
||||
data = get_data(__file__)
|
||||
|
||||
|
||||
def count_one_zero(xs, index):
|
||||
ones, zeros = 0, 0
|
||||
for x in xs:
|
||||
if x[index] == "1":
|
||||
ones += 1
|
||||
elif x[index] == "0":
|
||||
zeros += 1
|
||||
else:
|
||||
assert False
|
||||
return (ones, zeros)
|
||||
|
||||
|
||||
a = ""
|
||||
b = ""
|
||||
lines = list(data.strip().splitlines())
|
||||
for i in range(len(lines[0])):
|
||||
ones, zeros = count_one_zero(lines, i)
|
||||
if ones > zeros:
|
||||
a += "1"
|
||||
b += "0"
|
||||
elif zeros > ones:
|
||||
a += "0"
|
||||
b += "1"
|
||||
else:
|
||||
assert False
|
||||
a, b = int(a, 2), int(b, 2)
|
||||
print(a * b)
|
||||
|
||||
lines = list(data.strip().splitlines())
|
||||
for i in range(len(lines[0])):
|
||||
ones, zeros = count_one_zero(lines, i)
|
||||
c = "1" if ones >= zeros else "0"
|
||||
lines = [l for l in lines if l[i] == c]
|
||||
if len(lines) == 1:
|
||||
break
|
||||
(line,) = lines
|
||||
a = int(line, 2)
|
||||
|
||||
lines = list(data.strip().splitlines())
|
||||
for i in range(len(lines[0])):
|
||||
ones, zeros = count_one_zero(lines, i)
|
||||
c = "0" if zeros <= ones else "1"
|
||||
lines = [l for l in lines if l[i] == c]
|
||||
if len(lines) == 1:
|
||||
break
|
||||
|
||||
(line,) = lines
|
||||
b = int(line, 2)
|
||||
print(a * b)
|
||||
52
2021/d4.py
Normal file
52
2021/d4.py
Normal file
@@ -0,0 +1,52 @@
|
||||
from lib import get_data
|
||||
from lib import ints
|
||||
|
||||
data = get_data(__file__).strip()
|
||||
|
||||
boards = []
|
||||
numbers = None
|
||||
for i, p in enumerate(data.split("\n\n")):
|
||||
if i == 0:
|
||||
numbers = ints(p)
|
||||
else:
|
||||
board = []
|
||||
for line in p.splitlines():
|
||||
board.append(ints(line))
|
||||
boards.append(board)
|
||||
|
||||
assert numbers is not None
|
||||
first_won = None
|
||||
last_won = None
|
||||
boards_done = list()
|
||||
for n in numbers:
|
||||
for i in range(len(boards)):
|
||||
if i in boards_done:
|
||||
continue
|
||||
board = boards[i]
|
||||
board = [[f if n != f else None for f in row] for row in board]
|
||||
boards[i] = board
|
||||
for row in board:
|
||||
if all(cell is None for cell in row):
|
||||
if not i in boards_done:
|
||||
boards_done.append(i)
|
||||
for col in zip(*board):
|
||||
if all(cell is None for cell in col):
|
||||
if not i in boards_done:
|
||||
boards_done.append(i)
|
||||
|
||||
if len(boards_done) > 0 and first_won is None:
|
||||
first_won = (i, n)
|
||||
|
||||
if len(boards_done) == len(boards):
|
||||
last_won = (i, n)
|
||||
break
|
||||
if len(boards_done) == len(boards):
|
||||
break
|
||||
|
||||
assert first_won is not None
|
||||
assert last_won is not None
|
||||
|
||||
for i, n in [first_won, last_won]:
|
||||
board = boards[i]
|
||||
a = sum([sum([n if n is not None else 0 for n in row]) for row in board])
|
||||
print(a * n)
|
||||
39
2021/d5.py
Normal file
39
2021/d5.py
Normal file
@@ -0,0 +1,39 @@
|
||||
from lib import get_data
|
||||
from lib import ints
|
||||
from collections import defaultdict
|
||||
|
||||
data = get_data(__file__).strip()
|
||||
|
||||
counts = defaultdict(int)
|
||||
for line in data.splitlines():
|
||||
x1, y1, x2, y2 = ints(line)
|
||||
if x1 == x2 or y1 == y2:
|
||||
if x1 == x2:
|
||||
for y in range(min(y1, y2), max(y1, y2) + 1):
|
||||
counts[(x1, y)] += 1
|
||||
elif y1 == y2:
|
||||
for x in range(min(x1, x2), max(x1, x2) + 1):
|
||||
counts[(x, y1)] += 1
|
||||
else:
|
||||
assert False
|
||||
|
||||
r = sum(1 for v in counts.values() if v > 1)
|
||||
print(r)
|
||||
|
||||
|
||||
for line in data.splitlines():
|
||||
x1, y1, x2, y2 = ints(line)
|
||||
if x1 == x2 or y1 == y2:
|
||||
pass
|
||||
else:
|
||||
dx = 1 if x2 > x1 else -1
|
||||
dy = 1 if y2 > y1 else -1
|
||||
|
||||
while (x1, y1) != (x2, y2):
|
||||
counts[(x1, y1)] += 1
|
||||
x1 += dx
|
||||
y1 += dy
|
||||
counts[(x2, y2)] += 1
|
||||
|
||||
r = sum(1 for v in counts.values() if v > 1)
|
||||
print(r)
|
||||
32
2021/d6.py
Normal file
32
2021/d6.py
Normal file
@@ -0,0 +1,32 @@
|
||||
from lib import get_data
|
||||
from lib import ints
|
||||
from collections import defaultdict
|
||||
|
||||
data = get_data(__file__).strip()
|
||||
xs = ints(data)
|
||||
|
||||
for _ in range(80):
|
||||
for i in range(len(xs)):
|
||||
if xs[i] == 0:
|
||||
xs[i] = 6
|
||||
xs.append(8)
|
||||
else:
|
||||
xs[i] -= 1
|
||||
|
||||
print(len(xs))
|
||||
|
||||
xs = ints(data)
|
||||
fish = defaultdict(int)
|
||||
for x in xs:
|
||||
fish[x] += 1
|
||||
|
||||
for _ in range(256):
|
||||
new_fish = defaultdict(int)
|
||||
new_fish[8] = fish[0]
|
||||
new_fish[6] = fish[0]
|
||||
for i in range(1, 9):
|
||||
new_fish[i - 1] += fish[i]
|
||||
fish = new_fish
|
||||
|
||||
r = sum(fish.values())
|
||||
print(r)
|
||||
20
2021/d7.py
Normal file
20
2021/d7.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from lib import get_data
|
||||
from lib import ints
|
||||
|
||||
data = get_data(__file__).strip()
|
||||
xs = ints(data)
|
||||
|
||||
|
||||
def d(t, x):
|
||||
n = abs(t - x)
|
||||
return n * (n + 1) // 2
|
||||
|
||||
|
||||
s_min_1 = 10**16
|
||||
s_min_2 = 10**16
|
||||
for t in range(min(xs), max(xs) + 1):
|
||||
s_min_1 = min(s_min_1, sum(abs(t - x) for x in xs))
|
||||
s_min_2 = min(s_min_2, sum(d(t, x) for x in xs))
|
||||
|
||||
print(s_min_1)
|
||||
print(s_min_2)
|
||||
65
2021/d8.py
Normal file
65
2021/d8.py
Normal file
@@ -0,0 +1,65 @@
|
||||
from lib import get_data
|
||||
from itertools import permutations
|
||||
|
||||
data = get_data(__file__).strip()
|
||||
|
||||
code_to_num = {
|
||||
"cf": 1,
|
||||
"acf": 7,
|
||||
"bcdf": 4,
|
||||
"acdeg": 2,
|
||||
"acdfg": 3,
|
||||
"abdfg": 5,
|
||||
"abcefg": 0,
|
||||
"abdefg": 6,
|
||||
"abcdfg": 9,
|
||||
"abcdefg": 8,
|
||||
}
|
||||
|
||||
|
||||
def translate(inn, mapping):
|
||||
out = ""
|
||||
for c in inn:
|
||||
out += mapping[c]
|
||||
return "".join(sorted(out))
|
||||
|
||||
|
||||
def decode(inn, out):
|
||||
s = "abcdefg"
|
||||
m = None
|
||||
for p in permutations(s):
|
||||
m = {k: v for k, v in zip(p, s)}
|
||||
all = set(code_to_num.keys())
|
||||
for in_code in inn:
|
||||
out_code = translate(in_code, m)
|
||||
if out_code in all:
|
||||
all.remove(out_code)
|
||||
|
||||
if len(all) == 0:
|
||||
break
|
||||
else:
|
||||
m = None
|
||||
|
||||
assert m is not None
|
||||
|
||||
o = 0
|
||||
d = 1000
|
||||
for out_code in out:
|
||||
out_code = translate(out_code, m)
|
||||
v = code_to_num[out_code]
|
||||
o += d * v
|
||||
d //= 10
|
||||
return o
|
||||
|
||||
|
||||
s1 = 0
|
||||
s2 = 0
|
||||
for line in data.splitlines():
|
||||
inn, out = line.strip().split(" | ")
|
||||
inn = inn.split()
|
||||
out = out.split()
|
||||
s1 += sum(1 for v in out if len(v) in [2, 3, 4, 7])
|
||||
s2 += decode(inn, out)
|
||||
|
||||
print(s1)
|
||||
print(s2)
|
||||
47
2021/d9.py
Normal file
47
2021/d9.py
Normal file
@@ -0,0 +1,47 @@
|
||||
from lib import get_data, Grid2D
|
||||
from collections import deque
|
||||
|
||||
data = get_data(__file__).strip()
|
||||
g = Grid2D(data)
|
||||
|
||||
s = 0
|
||||
low_points = []
|
||||
for row in range(g.n_rows):
|
||||
for col in range(g.n_cols):
|
||||
h = int(g[(row, col)])
|
||||
for nb in g.neighbors_ort((row, col)):
|
||||
if int(g[nb]) <= h:
|
||||
break
|
||||
else:
|
||||
low_points.append((row, col))
|
||||
s += 1 + h
|
||||
|
||||
print(s)
|
||||
|
||||
basins = []
|
||||
for row, col in low_points:
|
||||
start = None
|
||||
visited = set()
|
||||
queue = deque([(row, col)])
|
||||
|
||||
while queue:
|
||||
vertex = queue.popleft()
|
||||
if vertex in visited:
|
||||
continue
|
||||
visited.add(vertex)
|
||||
|
||||
neighbors = []
|
||||
for neighbor in g.neighbors_ort(vertex):
|
||||
if neighbor in visited:
|
||||
continue
|
||||
|
||||
neighbor_height = int(g[neighbor])
|
||||
if neighbor_height < 9:
|
||||
queue.append(neighbor)
|
||||
|
||||
basins.append(len(visited))
|
||||
|
||||
s = 1
|
||||
for x in sorted(basins)[-3:]:
|
||||
s *= x
|
||||
print(s)
|
||||
1
2021/lib.py
Symbolic link
1
2021/lib.py
Symbolic link
@@ -0,0 +1 @@
|
||||
../lib.py
|
||||
21
2024/d01.py
Normal file
21
2024/d01.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from lib import get_data
|
||||
from lib import ints
|
||||
|
||||
data = get_data(__file__)
|
||||
|
||||
s = 0
|
||||
ass, bss = [], []
|
||||
s2 = 0
|
||||
|
||||
# I had an intuition for this and then went for loops...
|
||||
xs, ys = list(zip(*[ints(line) for line in data.splitlines()]))
|
||||
|
||||
xs = sorted(xs)
|
||||
ys = sorted(ys)
|
||||
|
||||
for a, b in zip(xs, ys):
|
||||
s += max(a, b) - min(a, b)
|
||||
s2 += ys.count(a) * a
|
||||
|
||||
print(s)
|
||||
print(s2)
|
||||
42
2024/d02.py
Normal file
42
2024/d02.py
Normal file
@@ -0,0 +1,42 @@
|
||||
from lib import get_data
|
||||
from lib import ints
|
||||
|
||||
|
||||
data = get_data(__file__)
|
||||
|
||||
s1 = 0
|
||||
s2 = 0
|
||||
|
||||
for line in data.splitlines():
|
||||
xs = ints(line)
|
||||
|
||||
deltas = []
|
||||
for i in range(len(xs) - 1):
|
||||
deltas.append(xs[i + 1] - xs[i])
|
||||
if abs(xs[i + 1] - xs[i]) not in [1, 2, 3]:
|
||||
break
|
||||
else:
|
||||
if deltas[0] < 0 and all(deltas[i] < 0 for i in range(len(deltas))):
|
||||
s1 += 1
|
||||
elif deltas[0] > 0 and all(deltas[i] > 0 for i in range(len(deltas))):
|
||||
s1 += 1
|
||||
|
||||
sublists = [xs[:i] + xs[i + 1 :] for i in range(len(xs))]
|
||||
found = False
|
||||
for xs in sublists:
|
||||
deltas = []
|
||||
for i in range(len(xs) - 1):
|
||||
deltas.append(xs[i + 1] - xs[i])
|
||||
if abs(xs[i + 1] - xs[i]) not in [1, 2, 3]:
|
||||
break
|
||||
else:
|
||||
if deltas[0] < 0 and all(deltas[i] < 0 for i in range(len(deltas))):
|
||||
|
||||
found = True
|
||||
if deltas[0] > 0 and all(deltas[i] > 0 for i in range(len(deltas))):
|
||||
found = True
|
||||
if found:
|
||||
s2 += 1
|
||||
|
||||
print(s1)
|
||||
print(s2)
|
||||
24
2024/d03.py
Normal file
24
2024/d03.py
Normal file
@@ -0,0 +1,24 @@
|
||||
import re
|
||||
from lib import get_data
|
||||
from lib import ints
|
||||
|
||||
|
||||
data = get_data(__file__)
|
||||
|
||||
t1 = 0
|
||||
t2 = 0
|
||||
enabled = True
|
||||
r = re.compile(r"mul\(\d+,\d+\)|do\(\)|don't\(\)")
|
||||
for m in r.findall(data):
|
||||
if m == "do()":
|
||||
enabled = True
|
||||
elif m == "don't()":
|
||||
enabled = False
|
||||
else:
|
||||
a, b = ints(m)
|
||||
t1 += a * b
|
||||
if enabled:
|
||||
t2 += a * b
|
||||
|
||||
print(t1)
|
||||
print(t2)
|
||||
44
2024/d04.py
Normal file
44
2024/d04.py
Normal file
@@ -0,0 +1,44 @@
|
||||
from lib import get_data
|
||||
from lib import Grid2D
|
||||
|
||||
data = get_data(__file__)
|
||||
g = Grid2D(data)
|
||||
|
||||
DIRS = {
|
||||
"N": (-1, 0),
|
||||
"E": (0, 1),
|
||||
"S": (1, 0),
|
||||
"W": (0, -1),
|
||||
"NE": (-1, 1),
|
||||
"SE": (1, 1),
|
||||
"SW": (1, -1),
|
||||
"NW": (-1, -1),
|
||||
}
|
||||
|
||||
t1, t2 = 0, 0
|
||||
for row in range(g.n_rows):
|
||||
for col in range(g.n_cols):
|
||||
for d in DIRS.values():
|
||||
coords = [(row + d[0] * i, col + d[1] * i) for i in range(len("XMAS"))]
|
||||
for (r, c), z in zip(coords, "XMAS"):
|
||||
if not (0 <= r < g.n_rows and 0 <= c < g.n_cols and g[(r, c)] == z):
|
||||
break
|
||||
else:
|
||||
t1 += 1
|
||||
|
||||
coords = [
|
||||
(row - 1, col - 1),
|
||||
(row - 1, col + 1),
|
||||
(row, col),
|
||||
(row + 1, col - 1),
|
||||
(row + 1, col + 1),
|
||||
]
|
||||
for s in ["MMASS", "MSAMS", "SSAMM", "SMASM"]:
|
||||
for (r, c), z in zip(coords, s):
|
||||
if not (0 <= r < g.n_rows and 0 <= c < g.n_cols and g[(r, c)] == z):
|
||||
break
|
||||
else:
|
||||
t2 += 1
|
||||
|
||||
print(t1)
|
||||
print(t2)
|
||||
37
2024/d05.py
Normal file
37
2024/d05.py
Normal file
@@ -0,0 +1,37 @@
|
||||
from lib import get_data
|
||||
from lib import ints
|
||||
|
||||
|
||||
def fix(xs):
|
||||
change = True
|
||||
while change:
|
||||
change = False
|
||||
for rule in rules.splitlines():
|
||||
x, y = ints(rule)
|
||||
if not (x in xs and y in xs):
|
||||
continue
|
||||
i, j = xs.index(x), xs.index(y)
|
||||
if not i < j:
|
||||
xs[i], xs[j] = xs[j], xs[i]
|
||||
change = True
|
||||
return xs
|
||||
|
||||
|
||||
rules, pages = get_data(__file__).split("\n\n")
|
||||
|
||||
t1, t2 = 0, 0
|
||||
for page in pages.splitlines():
|
||||
xs = ints(page)
|
||||
for rule in rules.splitlines():
|
||||
x, y = ints(rule)
|
||||
if not (x in xs and y in xs):
|
||||
continue
|
||||
if not (xs.index(x) < xs.index(y)):
|
||||
xs = fix(ints(page))
|
||||
t2 += xs[len(xs) // 2]
|
||||
break
|
||||
else:
|
||||
t1 += xs[len(xs) // 2]
|
||||
|
||||
print(t1)
|
||||
print(t2)
|
||||
51
2024/d06.py
Normal file
51
2024/d06.py
Normal file
@@ -0,0 +1,51 @@
|
||||
from lib import get_data
|
||||
from lib import Grid2D
|
||||
|
||||
data = get_data(__file__)
|
||||
|
||||
|
||||
DIRS = [
|
||||
(-1, 0),
|
||||
(0, 1),
|
||||
(1, 0),
|
||||
(0, -1),
|
||||
]
|
||||
|
||||
g = Grid2D(data)
|
||||
|
||||
t = 0
|
||||
for rr in range(g.n_rows):
|
||||
for cc in range(g.n_cols):
|
||||
(p,), dir = g.find("^"), 0
|
||||
seen = set()
|
||||
loop = False
|
||||
|
||||
if g[(rr, cc)] == "#":
|
||||
continue
|
||||
|
||||
if g[(rr, cc)] != "^":
|
||||
g[(rr, cc)] = "#"
|
||||
|
||||
while True:
|
||||
if (p, dir) in seen:
|
||||
loop = True
|
||||
break
|
||||
seen.add((p, dir))
|
||||
r, c = p[0] + DIRS[dir][0], p[1] + DIRS[dir][1]
|
||||
if not (0 <= r < g.n_rows and 0 <= c < g.n_cols):
|
||||
break
|
||||
|
||||
if g[(r, c)] == "#":
|
||||
dir = (dir + 1) % 4
|
||||
else:
|
||||
p = (r, c)
|
||||
|
||||
if g[(rr, cc)] == "^":
|
||||
print(len(set(pos for pos, _ in seen)))
|
||||
else:
|
||||
g[(rr, cc)] = "."
|
||||
|
||||
if loop:
|
||||
t += 1
|
||||
|
||||
print(t)
|
||||
30
2024/d07.py
Normal file
30
2024/d07.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from lib import get_data
|
||||
from lib import ints
|
||||
|
||||
data = get_data(__file__)
|
||||
|
||||
|
||||
def combs(acc, rest, part_2=False):
|
||||
if rest == []:
|
||||
return acc
|
||||
|
||||
nacc = []
|
||||
for a in acc:
|
||||
nacc.append(a + rest[0])
|
||||
nacc.append(a * rest[0])
|
||||
if part_2:
|
||||
nacc.append(int(str(a) + str(rest[0])))
|
||||
return combs(nacc, rest[1:], part_2)
|
||||
|
||||
|
||||
t1, t2 = 0, 0
|
||||
for line in data.splitlines():
|
||||
xs = ints(line)
|
||||
expected = xs[0]
|
||||
if xs[0] in combs([xs[1]], xs[2:]):
|
||||
t1 += xs[0]
|
||||
if xs[0] in combs([xs[1]], xs[2:], True):
|
||||
t2 += xs[0]
|
||||
|
||||
print(t1)
|
||||
print(t2)
|
||||
34
2024/d08.py
Normal file
34
2024/d08.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from lib import get_data
|
||||
from lib import Grid2D
|
||||
from itertools import combinations
|
||||
from collections import defaultdict
|
||||
|
||||
data = get_data(__file__)
|
||||
g = Grid2D(data)
|
||||
|
||||
d = defaultdict(list)
|
||||
|
||||
for row in range(g.n_rows):
|
||||
for col in range(g.n_cols):
|
||||
p = (row, col)
|
||||
if g[p] != ".":
|
||||
d[g[p]].append((row, col))
|
||||
|
||||
aa = set()
|
||||
bb = set()
|
||||
for coords in d.values():
|
||||
for c, d in combinations(coords, 2):
|
||||
bb.add(c)
|
||||
bb.add(d)
|
||||
for a, b in [(c, d), (d, c)]:
|
||||
d = b[0] - a[0], b[1] - a[1]
|
||||
a = b[0] + d[0], b[1] + d[1]
|
||||
if g.contains(a):
|
||||
aa.add(a)
|
||||
a = b[0] + d[0], b[1] + d[1]
|
||||
while g.contains(a):
|
||||
bb.add(a)
|
||||
a = a[0] + d[0], a[1] + d[1]
|
||||
|
||||
print(len(aa))
|
||||
print(len(bb))
|
||||
102
2024/d09.py
Normal file
102
2024/d09.py
Normal file
@@ -0,0 +1,102 @@
|
||||
from lib import get_data
|
||||
from copy import deepcopy
|
||||
|
||||
data = get_data(__file__)
|
||||
i = 0
|
||||
f = True
|
||||
d = []
|
||||
spaces = 0
|
||||
for c in data.strip():
|
||||
if f:
|
||||
d.append([int(c), i, f])
|
||||
f = False
|
||||
i += 1
|
||||
else:
|
||||
spaces += int(c)
|
||||
d.append([int(c), -1, f])
|
||||
f = True
|
||||
|
||||
d2 = deepcopy(d)
|
||||
|
||||
BLOCKS, ID, FULL = 0, 1, 2
|
||||
empty_i = 1
|
||||
full_i = len(d) - 1
|
||||
assert d[full_i][FULL]
|
||||
|
||||
while empty_i < full_i:
|
||||
empty = d[empty_i]
|
||||
full = d[full_i]
|
||||
|
||||
if empty[BLOCKS] == full[BLOCKS]:
|
||||
empty[ID] = full[ID]
|
||||
empty[FULL] = True
|
||||
full[BLOCKS] = 0
|
||||
full[FULL] = False
|
||||
elif empty[BLOCKS] < full[BLOCKS]:
|
||||
empty[ID] = full[ID]
|
||||
empty[FULL] = True
|
||||
full[BLOCKS] -= empty[BLOCKS]
|
||||
elif empty[BLOCKS] > full[BLOCKS]:
|
||||
new = [empty[BLOCKS] - full[BLOCKS], empty[ID], False]
|
||||
empty[ID] = full[ID]
|
||||
empty[BLOCKS] = full[BLOCKS]
|
||||
empty[FULL] = True
|
||||
d.insert(empty_i + 1, new)
|
||||
full_i += 1
|
||||
full[BLOCKS] = 0
|
||||
full[FULL] = False
|
||||
else:
|
||||
assert False
|
||||
|
||||
while d[empty_i][FULL] == True:
|
||||
empty_i += 1
|
||||
|
||||
while d[full_i][FULL] == False:
|
||||
full_i -= 1
|
||||
|
||||
t = 0
|
||||
i = 0
|
||||
for b in d:
|
||||
if b[FULL] == False:
|
||||
break
|
||||
for _ in range(b[BLOCKS]):
|
||||
t += i * b[ID]
|
||||
i += 1
|
||||
print(t)
|
||||
|
||||
d = d2
|
||||
|
||||
full_i = len(d) - 1
|
||||
while full_i > 0:
|
||||
full = d[full_i]
|
||||
prev_i = full_i
|
||||
for empty_i in range(0, full_i):
|
||||
empty = d[empty_i]
|
||||
if full[FULL] == False:
|
||||
continue
|
||||
if empty[FULL]:
|
||||
continue
|
||||
if empty[BLOCKS] < full[BLOCKS]:
|
||||
continue
|
||||
|
||||
if empty[BLOCKS] == full[BLOCKS]:
|
||||
empty[FULL] = True
|
||||
empty[ID] = full[ID]
|
||||
full[FULL] = False
|
||||
elif empty[BLOCKS] > full[BLOCKS]:
|
||||
full[FULL] = False
|
||||
new = [full[BLOCKS], full[ID], True]
|
||||
d[empty_i] = new
|
||||
d.insert(empty_i + 1, [empty[BLOCKS] - full[BLOCKS], -1, False])
|
||||
full_i += 1
|
||||
break
|
||||
full_i -= 1
|
||||
|
||||
t = 0
|
||||
i = 0
|
||||
for b in d:
|
||||
for _ in range(b[BLOCKS]):
|
||||
if b[FULL] == True:
|
||||
t += i * b[ID]
|
||||
i += 1
|
||||
print(t)
|
||||
81
2024/d09_2.py
Normal file
81
2024/d09_2.py
Normal file
@@ -0,0 +1,81 @@
|
||||
from lib import get_data
|
||||
|
||||
data = get_data("d9.py")
|
||||
|
||||
|
||||
def parse(data):
|
||||
xs = []
|
||||
f = True
|
||||
i = 0
|
||||
for c in data.strip():
|
||||
if f:
|
||||
for _ in range(int(c)):
|
||||
xs.append(i)
|
||||
f = False
|
||||
i += 1
|
||||
else:
|
||||
for _ in range(int(c)):
|
||||
xs.append(".")
|
||||
f = True
|
||||
return xs
|
||||
|
||||
|
||||
def score(xs):
|
||||
t = 0
|
||||
for i, j in enumerate(xs):
|
||||
if j == ".":
|
||||
continue
|
||||
t += i * j
|
||||
return t
|
||||
|
||||
|
||||
xs = parse(data)
|
||||
i, j = 0, len(xs) - 1
|
||||
while i <= j:
|
||||
while xs[i] != ".":
|
||||
i += 1
|
||||
|
||||
while xs[j] == ".":
|
||||
j -= 1
|
||||
|
||||
if not i <= j:
|
||||
break
|
||||
|
||||
xs[i], xs[j] = xs[j], xs[i]
|
||||
|
||||
print(score(xs))
|
||||
xs = parse(data)
|
||||
|
||||
idx = {}
|
||||
ids = []
|
||||
for i, x in enumerate(xs):
|
||||
if x == ".":
|
||||
continue
|
||||
if not x in idx:
|
||||
idx[x] = i
|
||||
ids.append(x)
|
||||
|
||||
while ids:
|
||||
id = ids.pop()
|
||||
i1, i2 = idx[id], idx[id]
|
||||
while i2 < len(xs) and xs[i2] == id:
|
||||
i2 += 1
|
||||
|
||||
j1 = 0
|
||||
while j1 < i1:
|
||||
while xs[j1] != ".":
|
||||
j1 += 1
|
||||
if not j1 < i1:
|
||||
break
|
||||
j2 = j1
|
||||
while xs[j2] == ".":
|
||||
j2 += 1
|
||||
assert j2 <= i1
|
||||
|
||||
if i2 - i1 <= j2 - j1:
|
||||
for j, i in zip(range(j1, j2), range(i1, i2)):
|
||||
xs[j], xs[i] = xs[i], xs[j]
|
||||
break
|
||||
j1 = j2
|
||||
|
||||
print(score(xs))
|
||||
26
2024/d10.py
Normal file
26
2024/d10.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from collections import deque
|
||||
from lib import Grid2D
|
||||
from lib import get_data
|
||||
|
||||
|
||||
data = get_data(__file__)
|
||||
g = Grid2D(data)
|
||||
|
||||
p1 = 0
|
||||
p2 = 0
|
||||
for start in g.find("0"):
|
||||
queue = deque([start])
|
||||
all = set()
|
||||
while queue:
|
||||
node = queue.popleft()
|
||||
if g[node] == "9":
|
||||
all.add(node)
|
||||
p2 += 1
|
||||
|
||||
for nb in g.neighbors_ort(node):
|
||||
if int(g[node]) + 1 == int(g[nb]):
|
||||
queue.append(nb)
|
||||
p1 += len(all)
|
||||
|
||||
print(p1)
|
||||
print(p2)
|
||||
31
2024/d11.py
Normal file
31
2024/d11.py
Normal file
@@ -0,0 +1,31 @@
|
||||
from lib import get_data
|
||||
from lib import ints
|
||||
|
||||
data = get_data(__file__)
|
||||
xs = ints(data)
|
||||
CACHE = {}
|
||||
|
||||
# I had maxsize too small at first :/
|
||||
# @lru_cache(maxsize=10**12)
|
||||
def apply(x, rest):
|
||||
if rest == 0:
|
||||
return 1
|
||||
|
||||
if (x, rest) in CACHE:
|
||||
return CACHE[(x, rest)]
|
||||
|
||||
if x == 0:
|
||||
r = apply(1, rest - 1)
|
||||
elif len(str(x)) % 2 == 0:
|
||||
sx = str(x)
|
||||
a = int(sx[: len(sx) // 2])
|
||||
b = int(sx[len(sx) // 2 :])
|
||||
r = apply(a, rest - 1) + apply(b, rest - 1)
|
||||
else:
|
||||
r = apply(x * 2024, rest - 1)
|
||||
CACHE[(x, rest)] = r
|
||||
return r
|
||||
|
||||
|
||||
print(sum([apply(x, 25) for x in xs]))
|
||||
print(sum([apply(x, 75) for x in xs]))
|
||||
61
2024/d12.py
Normal file
61
2024/d12.py
Normal file
@@ -0,0 +1,61 @@
|
||||
from lib import get_data
|
||||
from lib import Grid2D
|
||||
from collections import defaultdict
|
||||
from collections import deque
|
||||
|
||||
data = get_data(__file__)
|
||||
|
||||
DIRS = {
|
||||
"N": (-1, 0),
|
||||
"E": (0, 1),
|
||||
"S": (1, 0),
|
||||
"W": (0, -1),
|
||||
}.values()
|
||||
|
||||
|
||||
g = Grid2D(data)
|
||||
cs = defaultdict(set)
|
||||
for row in range(g.n_rows):
|
||||
for col in range(g.n_cols):
|
||||
c = g[(row, col)]
|
||||
cs[c].add((row, col))
|
||||
|
||||
t1, t2 = 0, 0
|
||||
for c, cs in cs.items():
|
||||
while cs:
|
||||
peri = 0
|
||||
start = cs.pop()
|
||||
seen = set()
|
||||
queue = deque([start])
|
||||
fences = set()
|
||||
while queue:
|
||||
pos = queue.popleft()
|
||||
if pos in seen:
|
||||
continue
|
||||
seen.add(pos)
|
||||
|
||||
for d in DIRS:
|
||||
nb = d[0] + pos[0], d[1] + pos[1]
|
||||
if g.contains(nb) and g[nb] == c:
|
||||
queue.append(nb)
|
||||
cs.discard(nb)
|
||||
else:
|
||||
peri += 1
|
||||
nb = tuple(list(nb) + [d])
|
||||
fences.add(nb)
|
||||
|
||||
nfences = set()
|
||||
for row, col, dir in fences:
|
||||
for dr, dc in [(1, 0), (0, 1)]:
|
||||
nr, nc = row + dr, col + dc
|
||||
if (nr, nc, dir) in fences:
|
||||
break
|
||||
else:
|
||||
nfences.add((row, col, dir))
|
||||
|
||||
c_area = len(seen)
|
||||
t1 += c_area * peri
|
||||
t2 += c_area * len(nfences)
|
||||
|
||||
print(t1)
|
||||
print(t2)
|
||||
33
2024/d13.py
Normal file
33
2024/d13.py
Normal file
@@ -0,0 +1,33 @@
|
||||
from lib import get_data
|
||||
from lib import ints
|
||||
|
||||
data = get_data(__file__)
|
||||
ps = data.split("\n\n")
|
||||
|
||||
c1, c2 = 0, 0
|
||||
for p in ps:
|
||||
lines = p.splitlines()
|
||||
ax, ay = ints(lines[0])
|
||||
bx, by = ints(lines[1])
|
||||
px, py = ints(lines[2])
|
||||
px2 = px + 10000000000000
|
||||
py2 = py + 10000000000000
|
||||
|
||||
for a in range(0, 101):
|
||||
for b in range(0, 101):
|
||||
if a * ax + b * bx == px and a * ay + b * by == py:
|
||||
c1 += a * 3 + b
|
||||
|
||||
D = ax * by - bx * ay
|
||||
if abs(D) > 1e-10:
|
||||
a_ = px2 * by - bx * py2
|
||||
b_ = ax * py2 - px2 * ay
|
||||
if a_ % D == 0 and b_ % D == 0:
|
||||
a = (px2 * by - bx * py2) // D
|
||||
b = (ax * py2 - px2 * ay) // D
|
||||
|
||||
if a >= 0 and b >= 0:
|
||||
c2 += a * 3 + b
|
||||
|
||||
print(c1)
|
||||
print(c2)
|
||||
64
2024/d14.py
Normal file
64
2024/d14.py
Normal file
@@ -0,0 +1,64 @@
|
||||
from lib import get_data
|
||||
from lib import ints
|
||||
from collections import defaultdict
|
||||
|
||||
rows = 103
|
||||
cols = 101
|
||||
data = get_data(__file__)
|
||||
|
||||
|
||||
def score(robots):
|
||||
quadrants = defaultdict(int)
|
||||
for _, r, c in robots:
|
||||
if r < rows // 2 and c < cols // 2:
|
||||
quadrants[1] += 1
|
||||
elif r < rows // 2 and c > cols // 2:
|
||||
quadrants[2] += 1
|
||||
elif r > rows // 2 and c < cols // 2:
|
||||
quadrants[3] += 1
|
||||
elif r > rows // 2 and c > cols // 2:
|
||||
quadrants[4] += 1
|
||||
t = 1
|
||||
for q in quadrants.values():
|
||||
t *= q
|
||||
return t
|
||||
|
||||
|
||||
def print_from_xy(xs):
|
||||
x_min = min(v[1] for v in xs)
|
||||
x_max = max(v[1] for v in xs)
|
||||
y_min = min(v[0] for v in xs)
|
||||
y_max = max(v[0] for v in xs)
|
||||
for y in range(y_min, y_max + 1):
|
||||
row = ""
|
||||
for x in range(x_min, x_max + 1):
|
||||
if (x, y) in xs:
|
||||
row += "#"
|
||||
else:
|
||||
row += " "
|
||||
print(row)
|
||||
|
||||
|
||||
robots_speed = {}
|
||||
robots = list()
|
||||
for i, line in enumerate(data.splitlines()):
|
||||
c, r, vc, vr = ints(line)
|
||||
robots_speed[i] = (vr, vc)
|
||||
robots.append((i, r, c))
|
||||
|
||||
for j in range(rows * cols):
|
||||
nrobots = list()
|
||||
count = 0
|
||||
for i, r, c in robots:
|
||||
vr, vc = robots_speed[i]
|
||||
nr = (r + vr) % rows
|
||||
nc = (c + vc) % cols
|
||||
nrobots.append((i, nr, nc))
|
||||
robots = nrobots
|
||||
if j == 99:
|
||||
print(score(robots))
|
||||
robots_coords = [(r, c) for _, r, c in robots]
|
||||
if len(robots_coords) == len(set(robots_coords)):
|
||||
print(j + 1)
|
||||
# print_from_xy(robots_coords)
|
||||
break
|
||||
92
2024/d15.py
Normal file
92
2024/d15.py
Normal file
@@ -0,0 +1,92 @@
|
||||
from lib import get_data
|
||||
from lib import Grid2D
|
||||
|
||||
DIRS = {
|
||||
"^": (-1, 0),
|
||||
">": (0, 1),
|
||||
"v": (1, 0),
|
||||
"<": (0, -1),
|
||||
}
|
||||
|
||||
data = get_data(__file__)
|
||||
data, moves = data.split("\n\n")
|
||||
|
||||
g = Grid2D(data)
|
||||
|
||||
|
||||
def simulate(grid: Grid2D, moves: str):
|
||||
(p,) = g.find("@")
|
||||
for c in moves:
|
||||
if c == "\n":
|
||||
continue
|
||||
d = DIRS[c]
|
||||
|
||||
to_move = set([p])
|
||||
row = to_move
|
||||
blocked = False
|
||||
while True:
|
||||
nrow = set()
|
||||
for (
|
||||
r,
|
||||
c,
|
||||
) in row:
|
||||
np = r + d[0], c + d[1]
|
||||
if g[np] == "#":
|
||||
blocked = True
|
||||
elif g[np] == "O":
|
||||
nrow.add(np)
|
||||
elif g[np] == "[":
|
||||
nrow.add(np)
|
||||
if d == (1, 0) or d == (-1, 0):
|
||||
nrow.add((np[0], np[1] + 1))
|
||||
elif g[np] == "]":
|
||||
if d == (1, 0) or d == (-1, 0):
|
||||
nrow.add((np[0], np[1] - 1))
|
||||
nrow.add(np)
|
||||
to_move |= row
|
||||
row = nrow
|
||||
if len(row) == 0 or blocked:
|
||||
break
|
||||
|
||||
if not blocked:
|
||||
to_place = {}
|
||||
p = p[0] + d[0], p[1] + d[1]
|
||||
|
||||
for tm in to_move:
|
||||
np = tm[0] + d[0], tm[1] + d[1]
|
||||
to_place[np] = g[tm]
|
||||
g[tm] = "."
|
||||
|
||||
for pos, c in to_place.items():
|
||||
g[pos] = c
|
||||
|
||||
|
||||
def score(g: Grid2D) -> int:
|
||||
os = g.find("O")
|
||||
if os:
|
||||
return sum(r * 100 + c for r, c in os)
|
||||
return sum(r * 100 + c for r, c in g.find("["))
|
||||
|
||||
|
||||
simulate(g, moves)
|
||||
print(score(g))
|
||||
|
||||
ndata = ""
|
||||
for c in data:
|
||||
if c == "#":
|
||||
ndata += "##"
|
||||
elif c == "O":
|
||||
ndata += "[]"
|
||||
elif c == ".":
|
||||
ndata += ".."
|
||||
elif c == "@":
|
||||
ndata += "@."
|
||||
else:
|
||||
ndata += c
|
||||
|
||||
g = Grid2D(ndata)
|
||||
(p,) = g.find("@")
|
||||
|
||||
simulate(g, moves)
|
||||
print(score(g))
|
||||
exit()
|
||||
96
2024/d16.py
Normal file
96
2024/d16.py
Normal file
@@ -0,0 +1,96 @@
|
||||
import heapq
|
||||
from collections import defaultdict
|
||||
from lib import get_data
|
||||
from lib import Grid2D
|
||||
|
||||
|
||||
data = get_data(__file__)
|
||||
|
||||
g = Grid2D(data)
|
||||
|
||||
|
||||
def search(start, end, dir):
|
||||
paths = set()
|
||||
|
||||
def reconstruct(parents, current):
|
||||
seen = set()
|
||||
to_visit = [current]
|
||||
while to_visit:
|
||||
current = to_visit.pop()
|
||||
if current in seen:
|
||||
continue
|
||||
seen.add(current)
|
||||
paths.add(current[0])
|
||||
for p in parents[current]:
|
||||
to_visit.append(p)
|
||||
|
||||
def dist(n1, n2):
|
||||
"""cost from node to node"""
|
||||
if n1 == 0 or n1 == n2:
|
||||
return 0
|
||||
p1, d1 = n1
|
||||
p2, d2 = n2
|
||||
if p1 != p2:
|
||||
return 1
|
||||
if d1 != d2:
|
||||
return 1000
|
||||
assert False
|
||||
|
||||
def h(node):
|
||||
"""heuristic function (never overestimate)"""
|
||||
pos = node[0]
|
||||
return abs(pos[0] - end[0]) + abs(pos[1] - end[1])
|
||||
|
||||
def is_goal(node):
|
||||
return node[0] == end
|
||||
|
||||
def neighbors(node):
|
||||
pos, dir = node
|
||||
npos = pos[0] + dir[0], pos[1] + dir[1]
|
||||
nbs = []
|
||||
if g.contains(npos) and g[npos] != "#":
|
||||
nbs.append((npos, dir))
|
||||
ndir = dir[1], -dir[0]
|
||||
nbs.append((pos, ndir))
|
||||
ndir = -dir[1], dir[0]
|
||||
nbs.append((pos, ndir))
|
||||
return nbs
|
||||
|
||||
starts = [(start, dir)]
|
||||
open_set = []
|
||||
g_score = {}
|
||||
cost = None
|
||||
|
||||
for start in starts:
|
||||
heapq.heappush(open_set, (h(start), start))
|
||||
g_score[start] = dist(0, start)
|
||||
|
||||
parents = defaultdict(list)
|
||||
while open_set:
|
||||
current_f_score, current = heapq.heappop(open_set)
|
||||
if is_goal(current):
|
||||
# assert current_f_score == g_score[current]
|
||||
gs = g_score[current]
|
||||
if cost is None or gs <= cost:
|
||||
cost = gs
|
||||
reconstruct(parents, current)
|
||||
else:
|
||||
break
|
||||
|
||||
for neighbor in neighbors(current):
|
||||
tentative_g_score = g_score[current] + dist(current, neighbor)
|
||||
if tentative_g_score <= g_score.get(neighbor, 10**12):
|
||||
parents[neighbor].append(current)
|
||||
g_score[neighbor] = tentative_g_score
|
||||
f_score = g_score[neighbor] + h(neighbor)
|
||||
heapq.heappush(open_set, (f_score, neighbor))
|
||||
return cost, paths
|
||||
|
||||
|
||||
(start,) = g.find("S")
|
||||
(end,) = g.find("E")
|
||||
dir = (0, 1)
|
||||
|
||||
cost, paths = search(start, end, dir)
|
||||
print(cost)
|
||||
print(len(paths))
|
||||
61
2024/d17.py
Normal file
61
2024/d17.py
Normal file
@@ -0,0 +1,61 @@
|
||||
from lib import get_data
|
||||
from lib import ints
|
||||
|
||||
|
||||
data = get_data(__file__)
|
||||
nums, prog = data.split("\n\n")
|
||||
regs = ints(nums)
|
||||
prog = ints(prog)
|
||||
|
||||
i = 0
|
||||
out = []
|
||||
while i < len(prog) - 1:
|
||||
inst, op = prog[i], prog[i + 1]
|
||||
assert op != 7
|
||||
combo = op if op not in [4, 5, 6] else regs[op - 4]
|
||||
literal = op
|
||||
match inst:
|
||||
case 0:
|
||||
regs[0] = regs[0] >> combo
|
||||
case 1:
|
||||
regs[1] = regs[1] ^ literal
|
||||
case 2:
|
||||
regs[1] = combo % 8
|
||||
case 3:
|
||||
if regs[0] != 0:
|
||||
i = literal - 2
|
||||
case 4:
|
||||
regs[1] = regs[1] ^ regs[2]
|
||||
case 5:
|
||||
out.append(combo % 8)
|
||||
case 6:
|
||||
regs[1] = regs[0] // (2**combo)
|
||||
case 7:
|
||||
regs[2] = regs[0] // (2**combo)
|
||||
case _:
|
||||
assert False
|
||||
i += 2
|
||||
|
||||
print(",".join(map(str, out)))
|
||||
|
||||
|
||||
def get_a(a_current, xs):
|
||||
if len(xs) == 0:
|
||||
return [a_current // 8]
|
||||
x = xs[0]
|
||||
aan = []
|
||||
for a_new in range(8):
|
||||
a = a_current + a_new
|
||||
b = a % 8
|
||||
assert b == a_new
|
||||
b = b ^ 1
|
||||
c = a >> b
|
||||
b = b ^ c
|
||||
b = b ^ 5
|
||||
if (b % 8) == x:
|
||||
aan += get_a(a << 3, xs[1:])
|
||||
return aan
|
||||
|
||||
|
||||
aa = get_a(0, list(reversed(prog)))
|
||||
print(min(aa))
|
||||
67
2024/d18.py
Normal file
67
2024/d18.py
Normal file
@@ -0,0 +1,67 @@
|
||||
import heapq
|
||||
from lib import get_data
|
||||
from lib import ints
|
||||
from lib import Grid2D
|
||||
|
||||
data = get_data(__file__)
|
||||
|
||||
size = 71
|
||||
grid = "\n".join(["." * size for _ in range(size)])
|
||||
|
||||
g = Grid2D(grid)
|
||||
for i, line in enumerate(data.splitlines()):
|
||||
x, y = ints(line)
|
||||
g[(y, x)] = "x"
|
||||
|
||||
start = (0, 0)
|
||||
end = (size - 1, size - 1)
|
||||
|
||||
def dist(n1, n2):
|
||||
"""cost from node to node"""
|
||||
if n1 == 0 or n1 == n2:
|
||||
return 0
|
||||
return 1
|
||||
|
||||
def h(node):
|
||||
"""heuristic function (never overestimate)"""
|
||||
return abs(node[0] - end[0]) + abs(node[1] - end[1])
|
||||
|
||||
def is_goal(node):
|
||||
return node == end
|
||||
|
||||
def neighbors(node):
|
||||
nbs = []
|
||||
for nb in g.neighbors_ort(node):
|
||||
if g[nb] != "x":
|
||||
nbs.append(nb)
|
||||
return nbs
|
||||
|
||||
starts = [start]
|
||||
open_set = []
|
||||
g_score = {}
|
||||
cost = None
|
||||
|
||||
for start in starts:
|
||||
heapq.heappush(open_set, (h(start), start))
|
||||
g_score[start] = dist(0, start)
|
||||
|
||||
while open_set:
|
||||
current_f_score, current = heapq.heappop(open_set)
|
||||
if is_goal(current):
|
||||
assert current_f_score == g_score[current]
|
||||
cost = g_score[current]
|
||||
break
|
||||
|
||||
for neighbor in neighbors(current):
|
||||
tentative_g_score = g_score[current] + dist(current, neighbor)
|
||||
if neighbor not in g_score or tentative_g_score < g_score[neighbor]:
|
||||
g_score[neighbor] = tentative_g_score
|
||||
f_score = g_score[neighbor] + h(neighbor)
|
||||
heapq.heappush(open_set, (f_score, neighbor))
|
||||
|
||||
if i == 1023:
|
||||
print(cost)
|
||||
|
||||
if cost is None:
|
||||
print(line)
|
||||
break
|
||||
30
2024/d19.py
Normal file
30
2024/d19.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from lib import get_data
|
||||
from functools import cache
|
||||
|
||||
data = get_data(__file__)
|
||||
lines = data.splitlines()
|
||||
patterns = set(lines[0].split(", "))
|
||||
|
||||
|
||||
@cache
|
||||
def valid(line):
|
||||
if line == "":
|
||||
return 1
|
||||
r = 0
|
||||
for j in range(1, len(line) + 1):
|
||||
sub = line[:j]
|
||||
if sub in patterns:
|
||||
r += valid(line[j:])
|
||||
return r
|
||||
|
||||
|
||||
p1, p2 = 0, 0
|
||||
lines = lines[2:]
|
||||
for line in lines:
|
||||
v = valid(line.strip())
|
||||
if v > 0:
|
||||
p1 += 1
|
||||
p2 += v
|
||||
|
||||
print(p1)
|
||||
print(p2)
|
||||
90
2024/d20.py
Normal file
90
2024/d20.py
Normal file
@@ -0,0 +1,90 @@
|
||||
from collections import deque
|
||||
from lib import get_data
|
||||
from lib import Grid2D
|
||||
|
||||
|
||||
data = """###############
|
||||
#...#...#.....#
|
||||
#.#.#.#.#.###.#
|
||||
#S#...#.#.#...#
|
||||
#######.#.#.###
|
||||
#######.#.#...#
|
||||
#######.#.###.#
|
||||
###..E#...#...#
|
||||
###.#######.###
|
||||
#...###...#...#
|
||||
#.#####.#.###.#
|
||||
#.#...#.#.#...#
|
||||
#.#.#.#.#.#.###
|
||||
#...#...#...###
|
||||
###############
|
||||
"""
|
||||
data = get_data(__file__)
|
||||
|
||||
g = Grid2D(data)
|
||||
|
||||
(start,) = g.find("S")
|
||||
(end,) = g.find("E")
|
||||
|
||||
cost = 0
|
||||
sd = dict()
|
||||
de = dict()
|
||||
|
||||
queue = deque([(start[0], start[1], 0)])
|
||||
seen = set()
|
||||
while queue:
|
||||
r, c, cost = queue.popleft()
|
||||
|
||||
if (r, c) in sd:
|
||||
continue
|
||||
sd[(r, c)] = cost
|
||||
|
||||
if (r, c) == end:
|
||||
break
|
||||
|
||||
for r, c in g.neighbors_ort((r, c)):
|
||||
if g[(r, c)] != "#":
|
||||
queue.append((r, c, cost + 1))
|
||||
|
||||
seen = set()
|
||||
queue = deque([(end[0], end[1], 0)])
|
||||
while queue:
|
||||
r, c, cost = queue.popleft()
|
||||
|
||||
if (r, c) in de:
|
||||
continue
|
||||
de[(r, c)] = cost
|
||||
|
||||
if (r, c) == start:
|
||||
break
|
||||
|
||||
for r, c in g.neighbors_ort((r, c)):
|
||||
if g[(r, c)] != "#":
|
||||
queue.append((r, c, cost + 1))
|
||||
|
||||
for max_steps in [2, 20]:
|
||||
cheats = set()
|
||||
for cr, cc in g.find("S."):
|
||||
start = cr, cc, 0
|
||||
seen = set()
|
||||
queue = deque([start])
|
||||
while queue:
|
||||
r, c, steps = queue.popleft()
|
||||
if steps > max_steps:
|
||||
continue
|
||||
|
||||
if (r, c) in seen:
|
||||
continue
|
||||
seen.add((r, c))
|
||||
|
||||
if g[(r, c)] != "#":
|
||||
ncost = sd[(cr, cc)] + steps + de[(r, c)]
|
||||
if cost - ncost >= 100:
|
||||
cheats.add((cr, cc, r, c))
|
||||
|
||||
for dr, dc in [(-1, 0), (0, 1), (1, 0), (0, -1)]:
|
||||
nr, nc = r + dr, c + dc
|
||||
if g.contains((nr, nc)):
|
||||
queue.append((nr, nc, steps + 1))
|
||||
|
||||
print(len(cheats))
|
||||
87
2024/d21.py
Normal file
87
2024/d21.py
Normal file
@@ -0,0 +1,87 @@
|
||||
from lib import get_data
|
||||
from collections import deque
|
||||
from functools import cache
|
||||
|
||||
|
||||
data = get_data(__file__)
|
||||
# data = """029A
|
||||
# 980A
|
||||
# 179A
|
||||
# 456A
|
||||
# 379A"""
|
||||
|
||||
|
||||
def new_dir(current, move):
|
||||
return {
|
||||
"<": {">": "v"},
|
||||
"^": {">": "A", "v": "v"},
|
||||
"v": {"<": "<", ">": ">", "^": "^"},
|
||||
">": {"^": "A", "<": "v"},
|
||||
"A": {"<": "^", "v": ">"},
|
||||
}[current].get(move, None)
|
||||
|
||||
|
||||
def new_num(current, move):
|
||||
return {
|
||||
"7": {">": "8", "v": "4", },
|
||||
"8": {"<": "7", ">": "9", "v": "5"},
|
||||
"9": {"<": "8", "v": "6"},
|
||||
"4": {"^": "7", ">": "5", "v": "1"},
|
||||
"5": {"^": "8", ">": "6", "<": "4", "v": "2"},
|
||||
"6": {"^": "9", "<": "5", "v": "3"},
|
||||
"1": {"^": "4", ">": "2"},
|
||||
"2": {"^": "5", ">": "3", "<": "1", "v": "0"},
|
||||
"3": {"^": "6", "<": "2", "v": "A"},
|
||||
"0": {"^": "2", ">": "A"},
|
||||
"A": {"^": "3", "<": "0"},
|
||||
}[current].get(move, None)
|
||||
|
||||
|
||||
@cache
|
||||
def find(code, depth, lookup):
|
||||
if depth == 0:
|
||||
return len(code)
|
||||
code = list(code)
|
||||
t = 0
|
||||
current = "A"
|
||||
while code:
|
||||
solutions = []
|
||||
target = code.pop(0)
|
||||
start = (current, "")
|
||||
seen = set()
|
||||
queue = deque([start])
|
||||
while queue:
|
||||
node = queue.popleft()
|
||||
if node in seen:
|
||||
continue
|
||||
seen.add(node)
|
||||
if node[0] == target:
|
||||
solution = node[1] + "A"
|
||||
if len(solutions) == 0:
|
||||
solutions.append(solution)
|
||||
elif len(solutions[-1]) == len(solution):
|
||||
solutions.append(solution)
|
||||
else:
|
||||
break
|
||||
continue
|
||||
|
||||
for c in "<>v^":
|
||||
next = lookup(node[0], c)
|
||||
if next is not None:
|
||||
queue.append((next, node[1] + c))
|
||||
current = target
|
||||
t += min(find(code, depth - 1, new_dir) for code in solutions)
|
||||
return t
|
||||
|
||||
|
||||
p1, p2 = 0, 0
|
||||
for line in data.splitlines():
|
||||
i = int("".join([c for c in line if c.isdigit()]))
|
||||
s1 = find(line, 3, new_num)
|
||||
s2 = find(line, 26, new_num)
|
||||
p1 += s1 * i
|
||||
p2 += s2 * i
|
||||
|
||||
|
||||
print(p1)
|
||||
print(p2)
|
||||
56
2024/d22.py
Normal file
56
2024/d22.py
Normal file
@@ -0,0 +1,56 @@
|
||||
from lib import get_data
|
||||
from lib import ints
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
data = get_data(__file__)
|
||||
|
||||
|
||||
def secret(x):
|
||||
x ^= x * 64
|
||||
x %= 16777216
|
||||
|
||||
x ^= x // 32
|
||||
x %= 16777216
|
||||
|
||||
x ^= x * 2048
|
||||
x %= 16777216
|
||||
return x
|
||||
|
||||
|
||||
def slice(xs, n):
|
||||
return [slice for slice in zip(*[xs[i:] for i in range(n)])]
|
||||
|
||||
|
||||
codes = defaultdict(int)
|
||||
|
||||
|
||||
def int_seq(x):
|
||||
xs = []
|
||||
for _ in range(2000):
|
||||
xs.append(x % 10)
|
||||
x = secret(x)
|
||||
ds = [a - b for a, b in zip(xs[1:], xs)]
|
||||
|
||||
seen = set()
|
||||
for s in slice(list(zip(xs[1:], ds)), 4):
|
||||
code = tuple([e[1] for e in s])
|
||||
v = s[-1][0]
|
||||
if not code in seen:
|
||||
codes[code] += v
|
||||
seen.add(code)
|
||||
|
||||
|
||||
for line in data.splitlines():
|
||||
(x,) = ints(line)
|
||||
|
||||
s = 0
|
||||
for line in data.splitlines():
|
||||
(x,) = ints(line)
|
||||
int_seq(x)
|
||||
for _ in range(2000):
|
||||
x = secret(x)
|
||||
s += x
|
||||
|
||||
print(s)
|
||||
print(max(codes.values()))
|
||||
43
2024/d23.py
Normal file
43
2024/d23.py
Normal file
@@ -0,0 +1,43 @@
|
||||
from lib import get_data
|
||||
from collections import defaultdict
|
||||
|
||||
data = get_data(__file__)
|
||||
g = defaultdict(set)
|
||||
|
||||
for line in data.splitlines():
|
||||
a, b = line.split("-")
|
||||
g[a].add(b)
|
||||
g[b].add(a)
|
||||
|
||||
|
||||
cs = set()
|
||||
for a, bs in g.items():
|
||||
for b in bs:
|
||||
for c in g[b]:
|
||||
if c in bs:
|
||||
cs.add(tuple(sorted([a, b, c])))
|
||||
|
||||
cs = [t for t in cs if any(x.startswith("t") for x in t)]
|
||||
print(len(cs))
|
||||
|
||||
|
||||
def find_largest_clique(graph):
|
||||
def bron_kerbosch(r, p, x, max_clique):
|
||||
if not p and not x:
|
||||
if len(r) > len(max_clique[0]):
|
||||
max_clique[0] = r.copy()
|
||||
return
|
||||
|
||||
for v in p.copy():
|
||||
neighbors = graph[v]
|
||||
bron_kerbosch(r | {v}, p & neighbors, x & neighbors, max_clique)
|
||||
p.remove(v)
|
||||
x.add(v)
|
||||
|
||||
max_clique = [set()]
|
||||
bron_kerbosch(set(), set(graph.keys()), set(), max_clique)
|
||||
return max_clique[0]
|
||||
|
||||
|
||||
cs = find_largest_clique(g)
|
||||
print(",".join(sorted(cs)))
|
||||
143
2024/d24.py
Normal file
143
2024/d24.py
Normal file
@@ -0,0 +1,143 @@
|
||||
from lib import get_data
|
||||
from lib import ints
|
||||
|
||||
LHS, OP, RHS, _, OUT = 0, 1, 2, 3, 4
|
||||
|
||||
|
||||
def run(states, gates):
|
||||
change = True
|
||||
while change:
|
||||
change = False
|
||||
for gate in gates:
|
||||
i1, op, i2, _, out = gate
|
||||
if i1 in states and i2 in states and out not in states:
|
||||
change = True
|
||||
outv = None
|
||||
match op:
|
||||
case "OR":
|
||||
outv = states[i1] | states[i2]
|
||||
case "XOR":
|
||||
outv = states[i1] ^ states[i2]
|
||||
case "AND":
|
||||
outv = states[i1] & states[i2]
|
||||
assert outv is not None
|
||||
states[out] = outv
|
||||
|
||||
i, t = 0, 0
|
||||
while True:
|
||||
si = f"z{i:02}"
|
||||
if not si in states:
|
||||
break
|
||||
if states[si]:
|
||||
t += 2**i
|
||||
i += 1
|
||||
return t
|
||||
|
||||
|
||||
data = get_data(__file__)
|
||||
|
||||
s1, s2 = data.split("\n\n")
|
||||
states = {}
|
||||
for line in s1.splitlines():
|
||||
gate, value = line.split(": ")
|
||||
states[gate] = int(value)
|
||||
|
||||
gates = []
|
||||
out_to_index = {}
|
||||
xors = {}
|
||||
zz = []
|
||||
for i, line in enumerate(s2.splitlines()):
|
||||
o1, op, o2, _, out = line.split()
|
||||
o1, o2 = sorted([o1, o2])
|
||||
gate = [o1, op, o2, "->", out]
|
||||
gates.append(gate)
|
||||
out_to_index[out] = i
|
||||
if out.startswith("z"):
|
||||
zz.append(out)
|
||||
if op == "XOR" and o1.startswith("x"):
|
||||
(index,) = ints(o1)
|
||||
xors[index] = gate
|
||||
|
||||
p1 = run(states, gates)
|
||||
print(p1)
|
||||
|
||||
NUMBER_Z_GATES = ints(sorted(zz)[-1])[0] + 1
|
||||
# print(NUMBER_Z_GATES)
|
||||
# pprint(xors)
|
||||
|
||||
xx = [f"x{i:02}" for i in range(NUMBER_Z_GATES - 1)]
|
||||
yy = [f"y{i:02}" for i in range(NUMBER_Z_GATES - 1)]
|
||||
|
||||
# def resolve(x):
|
||||
# if x in xx or x in yy:
|
||||
# return f"{x}"
|
||||
# # return x
|
||||
# a, op, b = gate_to_in[x]
|
||||
# aa = resolve(a)
|
||||
# bb = resolve(b)
|
||||
# # print(aa, bb)
|
||||
# aa, bb = sorted([aa, bb], reverse=True)
|
||||
# op = "^" if op == "XOR" else ("|" if op == "OR" else "&")
|
||||
# return f"({aa} {op} {bb})"
|
||||
# # return [aa, op, bb]
|
||||
|
||||
|
||||
def find_xor(index):
|
||||
xor = xors[index][OUT]
|
||||
for gate in gates:
|
||||
if gate[OP] == "XOR" and (gate[LHS] == xor or gate[RHS] == xor):
|
||||
return gate
|
||||
return None
|
||||
|
||||
|
||||
corrected = []
|
||||
for i in range(NUMBER_Z_GATES):
|
||||
sx, sy, sz = [f"{c}{i:02}" for c in "xyz"]
|
||||
gate = gates[out_to_index[sz]]
|
||||
if i == 0:
|
||||
assert gate == xors[i]
|
||||
elif i == NUMBER_Z_GATES - 1:
|
||||
pass # last gate needs special check
|
||||
else:
|
||||
fixed = False
|
||||
xor = xors[i]
|
||||
if gate[OP] != "XOR":
|
||||
cgate = find_xor(i)
|
||||
assert cgate is not None
|
||||
gate[OUT], cgate[OUT] = cgate[OUT], gate[OUT]
|
||||
corrected.append(gate[OUT])
|
||||
corrected.append(cgate[OUT])
|
||||
out_to_index[gate[OUT]], out_to_index[cgate[OUT]] = (
|
||||
out_to_index[cgate[OUT]],
|
||||
out_to_index[gate[OUT]],
|
||||
)
|
||||
fixed = True
|
||||
# print(f"[swap] {gate} <-> {cgate}")
|
||||
gate = gates[out_to_index[sz]]
|
||||
assert gate[OP] == "XOR"
|
||||
|
||||
# print(gate)
|
||||
i_lhs, i_rhs = out_to_index[gate[LHS]], out_to_index[gate[RHS]]
|
||||
a, b = gates[i_lhs], gates[i_rhs]
|
||||
|
||||
if a == xor:
|
||||
# print(f"[ok] {sz} ({fixed})")
|
||||
pass
|
||||
elif b == xor:
|
||||
# print(f"[ok] {sz} ({fixed})")
|
||||
gate[0], gate[2] = gate[2], gate[0]
|
||||
else:
|
||||
if a[OP] == "AND":
|
||||
a[OUT], xor[OUT] = xor[OUT], a[OUT]
|
||||
corrected.append(a[OUT])
|
||||
corrected.append(xor[OUT])
|
||||
# print(f"[swap] {a} <-> {xor}")
|
||||
out_to_index[a[OUT]], out_to_index[xor[OUT]] = (
|
||||
out_to_index[xor[OUT]],
|
||||
out_to_index[a[OUT]],
|
||||
)
|
||||
# print(f"[ok] {sz} (manual fix)")
|
||||
else:
|
||||
print(f"[nok] {sz} (don't know how to fix)")
|
||||
|
||||
print(",".join(sorted(corrected)))
|
||||
25
2024/d25.py
Normal file
25
2024/d25.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from lib import get_data
|
||||
from lib import Grid2D
|
||||
|
||||
data = get_data(__file__)
|
||||
|
||||
locks = []
|
||||
keys = []
|
||||
|
||||
for o in data.split("\n\n"):
|
||||
g = Grid2D(o)
|
||||
if all(g[(0, c)] == "#" for c in range(g.n_cols)):
|
||||
keys.append(g)
|
||||
elif all(g[g.n_rows - 1, c] == "#" for c in range(g.n_cols)):
|
||||
locks.append(g)
|
||||
else:
|
||||
assert False
|
||||
|
||||
t = 0
|
||||
for key in keys:
|
||||
for lock in locks:
|
||||
k = key.find("#")
|
||||
l = lock.find("#")
|
||||
if len(k) + len(l) == len(set(k) | set(l)):
|
||||
t += 1
|
||||
print(t)
|
||||
1
2024/lib.py
Symbolic link
1
2024/lib.py
Symbolic link
@@ -0,0 +1 @@
|
||||
../lib.py
|
||||
22
2025/d01.py
Normal file
22
2025/d01.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from lib import get_data
|
||||
|
||||
NUM = 100
|
||||
|
||||
data = get_data(__file__)
|
||||
|
||||
pos = 50
|
||||
r1, r2 = 0, 0
|
||||
for line in data.splitlines():
|
||||
d = line[0]
|
||||
n = int(line[1:])
|
||||
dir = 1 if d == "R" else -1
|
||||
|
||||
for _ in range(n):
|
||||
pos = (pos + dir) % NUM
|
||||
if pos == 0:
|
||||
r2 += 1
|
||||
if pos == 0:
|
||||
r1 += 1
|
||||
|
||||
print(r1)
|
||||
print(r2)
|
||||
34
2025/d02.py
Normal file
34
2025/d02.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from lib import get_data
|
||||
|
||||
|
||||
data = get_data(__file__)
|
||||
|
||||
def is_invalid(xs: str) -> bool:
|
||||
for seq_len in range(1, len(xs) // 2 + 1):
|
||||
if xs[:seq_len] == xs[seq_len:]:
|
||||
return True
|
||||
return False
|
||||
|
||||
def is_invalid2(xs: str) -> bool:
|
||||
for seq_len in range(1, len(xs)):
|
||||
if len(xs) % seq_len != 0:
|
||||
continue
|
||||
for repeat in range(len(xs) // seq_len):
|
||||
i = seq_len * repeat
|
||||
if xs[:seq_len] != xs[i:i + seq_len]:
|
||||
break
|
||||
else:
|
||||
return True
|
||||
return False
|
||||
|
||||
r1, r2 = 0, 0
|
||||
for id_range in data.split(","):
|
||||
lo, up = list(map(int, id_range.split("-")))
|
||||
for id in range(lo, up + 1):
|
||||
if is_invalid(str(id)):
|
||||
r1 += id
|
||||
if is_invalid2(str(id)):
|
||||
r2 += id
|
||||
|
||||
print(r1)
|
||||
print(r2)
|
||||
34
2025/d03.py
Normal file
34
2025/d03.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from lib import get_data
|
||||
|
||||
data = get_data(__file__)
|
||||
|
||||
|
||||
def max_joltage(xs: str, target_len: int) -> int:
|
||||
def dynamic(xs: str) -> dict[int, str]:
|
||||
if len(xs) == 0:
|
||||
return {0: ""}
|
||||
|
||||
rs = dynamic(xs[1:])
|
||||
for i in range(target_len, 0, -1):
|
||||
if not (i - 1) in rs:
|
||||
continue
|
||||
nv = int(xs[0] + rs[i - 1])
|
||||
if not i in rs:
|
||||
rs[i] = str(nv)
|
||||
elif nv > int(rs[i]):
|
||||
rs[i] = str(nv)
|
||||
return rs
|
||||
|
||||
rs = dynamic(xs)
|
||||
return int(rs[target_len])
|
||||
|
||||
|
||||
r1 = 0
|
||||
r2 = 0
|
||||
for line in data.splitlines():
|
||||
r1 += max_joltage(line, 2)
|
||||
r2 += max_joltage(line, 12)
|
||||
|
||||
|
||||
print(r1)
|
||||
print(r2)
|
||||
32
2025/d04.py
Normal file
32
2025/d04.py
Normal file
@@ -0,0 +1,32 @@
|
||||
from lib import get_data, Grid2D
|
||||
|
||||
data = get_data(__file__)
|
||||
g = Grid2D(data)
|
||||
|
||||
r = 0
|
||||
adjs = []
|
||||
for pos in g.all_coords():
|
||||
if g[pos] != "@":
|
||||
continue
|
||||
nr_of_rolls_adj = len([nb for nb in g.neighbors_adj(pos) if g[nb] == "@"])
|
||||
if nr_of_rolls_adj < 4:
|
||||
r += 1
|
||||
adjs.append(pos)
|
||||
print(r)
|
||||
|
||||
r = 0
|
||||
while True:
|
||||
adjs = []
|
||||
for pos in g.all_coords():
|
||||
if g[pos] != "@":
|
||||
continue
|
||||
nr_of_rolls_adj = len([nb for nb in g.neighbors_adj(pos) if g[nb] == "@"])
|
||||
if nr_of_rolls_adj < 4:
|
||||
adjs.append(pos)
|
||||
if adjs:
|
||||
r += len(adjs)
|
||||
for a in adjs:
|
||||
g[a] = "."
|
||||
else:
|
||||
break
|
||||
print(r)
|
||||
45
2025/d05.py
Normal file
45
2025/d05.py
Normal file
@@ -0,0 +1,45 @@
|
||||
from lib import get_data
|
||||
|
||||
data = get_data(__file__)
|
||||
|
||||
ranges, ids = data.split("\n\n")
|
||||
ids = tuple(map(int, ids.splitlines()))
|
||||
ranges = [list(map(int, line.split("-"))) for line in ranges.splitlines()]
|
||||
|
||||
updated = True
|
||||
while updated:
|
||||
updated = False
|
||||
sorted_ranges = []
|
||||
for lo, up in ranges:
|
||||
assert lo <= up
|
||||
for i in range(len(sorted_ranges)):
|
||||
slo, sup = sorted_ranges[i]
|
||||
if up + 1 < slo:
|
||||
sorted_ranges.insert(i, (lo, up))
|
||||
elif lo < slo:
|
||||
sorted_ranges[i] = (lo, max(up, sup))
|
||||
updated = True
|
||||
elif lo <= sup:
|
||||
sorted_ranges[i] = (slo, max(up, sup))
|
||||
updated = True
|
||||
else:
|
||||
continue
|
||||
break
|
||||
else:
|
||||
sorted_ranges.append((lo, up))
|
||||
ranges = sorted_ranges
|
||||
|
||||
|
||||
result_part_1 = 0
|
||||
for id_to_check in ids:
|
||||
for lo, up in ranges:
|
||||
if lo <= id_to_check <= up:
|
||||
result_part_1 += 1
|
||||
break
|
||||
print(result_part_1)
|
||||
|
||||
result_part_2 = 0
|
||||
for lo, up in ranges:
|
||||
result_part_2 += up - lo + 1
|
||||
print(result_part_2)
|
||||
assert result_part_2 == 350780324308385
|
||||
60
2025/d06.py
Normal file
60
2025/d06.py
Normal file
@@ -0,0 +1,60 @@
|
||||
from lib import get_data
|
||||
from typing import TypeVar
|
||||
|
||||
T = TypeVar('T')
|
||||
|
||||
data = """123 328 51 64
|
||||
45 64 387 23
|
||||
6 98 215 314
|
||||
* + * + """
|
||||
data = get_data(__file__)
|
||||
|
||||
def mul(xs):
|
||||
r = 1
|
||||
for x in xs:
|
||||
r *= x
|
||||
return r
|
||||
|
||||
lines = zip(*[line.split() for line in data.splitlines()])
|
||||
|
||||
r = 0
|
||||
for xs in lines:
|
||||
xs, op = xs[:-1], xs[-1]
|
||||
xs = list(map(int, xs))
|
||||
if op == "+":
|
||||
r += sum(xs)
|
||||
elif op == "*":
|
||||
r += mul(xs)
|
||||
else:
|
||||
assert False, "Unexpected op"
|
||||
print(r)
|
||||
|
||||
def split(xs: list[T], delim: T) -> list[list[T]]:
|
||||
res = [[]]
|
||||
for x in xs:
|
||||
if x == delim:
|
||||
res.append([])
|
||||
else:
|
||||
res[-1].append(x)
|
||||
return res
|
||||
|
||||
chars_trans: list[tuple[str]] = list(zip(*[line for line in data.splitlines()]))
|
||||
lines_trans: list[str] = list(map(lambda line: "".join(line).strip(), chars_trans))
|
||||
cols: list[list[str]] = split(lines_trans, '')
|
||||
|
||||
r2 = 0
|
||||
for xs in cols:
|
||||
x0 = xs[0]
|
||||
acc, op = int(x0[:-1]), x0[-1]
|
||||
for x in xs[1:]:
|
||||
x = int(x)
|
||||
if op == "+":
|
||||
acc += x
|
||||
elif op == "*":
|
||||
acc *= x
|
||||
else:
|
||||
assert False, "Unexpected op"
|
||||
r2 += acc
|
||||
|
||||
print(r2)
|
||||
|
||||
59
2025/d07.py
Normal file
59
2025/d07.py
Normal file
@@ -0,0 +1,59 @@
|
||||
from lib import get_data, Grid2D
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
data = """.......S.......
|
||||
...............
|
||||
.......^.......
|
||||
...............
|
||||
......^.^......
|
||||
...............
|
||||
.....^.^.^.....
|
||||
...............
|
||||
....^.^...^....
|
||||
...............
|
||||
...^.^...^.^...
|
||||
...............
|
||||
..^...^.....^..
|
||||
...............
|
||||
.^.^.^.^.^...^.
|
||||
..............."""
|
||||
data = get_data(__file__)
|
||||
|
||||
g = Grid2D(data)
|
||||
beams = [g.find('S')[0][1]]
|
||||
|
||||
splits = 0
|
||||
for row in range(1, g.n_rows - 1):
|
||||
new_beams = set()
|
||||
for beam in beams:
|
||||
field = g[(row, beam)]
|
||||
if field == "^":
|
||||
splits += 1
|
||||
new_beams.add(beam - 1)
|
||||
new_beams.add(beam + 1)
|
||||
elif field == ".":
|
||||
new_beams.add(beam)
|
||||
else:
|
||||
assert False, "unexpected field"
|
||||
beams = list(new_beams)
|
||||
# print(beams)
|
||||
|
||||
print(splits)
|
||||
|
||||
beams = {g.find('S')[0][1]: 1}
|
||||
for row in range(1, g.n_rows - 1):
|
||||
new_beams = defaultdict(int)
|
||||
for beam, count in beams.items():
|
||||
field = g[(row, beam)]
|
||||
if field == "^":
|
||||
new_beams[beam - 1] += count
|
||||
new_beams[beam + 1] += count
|
||||
elif field == ".":
|
||||
new_beams[beam] += count
|
||||
else:
|
||||
assert False, "unexpected field"
|
||||
beams = new_beams
|
||||
|
||||
print(sum(beams.values()))
|
||||
|
||||
1
2025/lib.py
Symbolic link
1
2025/lib.py
Symbolic link
@@ -0,0 +1 @@
|
||||
../lib.py
|
||||
496
README.md
496
README.md
@@ -2,211 +2,60 @@
|
||||
|
||||
Solutions and utility script for Advent of Code challenges in Python.
|
||||
|
||||
## AoC 2015
|
||||
## AoC 2025
|
||||
|
||||
- Day 1: 3:10
|
||||
- Day 2: 5:24 :/
|
||||
- Day 3: 7:26 ... :/
|
||||
- Day 4: 5:11 ...
|
||||
- Day 5: 14:05 o.O
|
||||
- Day 6: 15:00
|
||||
- Day 7: 17:00
|
||||
- Day 8: 14:55
|
||||
- Day 9: 13:48
|
||||
- Day 10: 70:00 ... slow, but fun
|
||||
- Day 11: 12:30
|
||||
- Day 12: 6:03
|
||||
- Day 13: 7:06 (would have been first by a minute, probably 70ish in 2023)
|
||||
- Day 14: 27:45 ... that was weak, logic error for part 2 :/
|
||||
- Day 15: 16:00 I should probably stop brute forcing these optimization problems
|
||||
- Day 16: 19:00
|
||||
- Day 17: 9:05
|
||||
- Day 18: 10:39
|
||||
- Day 19: Many days... yeah this one took me way too long to figure out
|
||||
- Day 20: 10:54
|
||||
- Day 21: 25:52 cute bug where I didn't consider that no armor is an option
|
||||
- Day 22: That was bad. Did not know how to choose between dfs/bfs and logic errors.
|
||||
- Day 23: 10:00
|
||||
- Day 24: 20:00 ugly - recursive solution would be more elegant
|
||||
- Day 25: 9:34
|
||||
Only twelve problems and no leaderboard this year. That means life will be less
|
||||
stressful and this will actually be more fun. Thank you Eric Wastl and let's go!
|
||||
|
||||
## AoC 2016
|
||||
- Day 1: Maybe the hardest day 1 part 2 ever for me? I struggled to calculate
|
||||
the number of ticks on 0 directly and ended up just iterating over all the
|
||||
ticks naively. Maybe I should revisit this.
|
||||
- Day 2: The simple direct approach with iterating over all the IDs in the
|
||||
ranges just worked okay. Not pretty but acceptable.
|
||||
- Day 3: Fun dynamic programming problem. Part 2 took me a little too long
|
||||
overall but it was fun.
|
||||
- Day 4: One of the easier grid based puzzles. Less than five minutes with
|
||||
existing grid library.
|
||||
- Day 5: A problem with ranges; I decided to implement proper range merging this
|
||||
time because I always kind of avoid that. I could probably decrease the
|
||||
complexity from O(n^2) to O(n log(n)) but I am happy that I've implemented
|
||||
merging at all.
|
||||
- Day 6: Transposing some rows and cols. Fun and good to keep the brain fit
|
||||
but not really hard. I would have been way too slow for the leaderboard
|
||||
in pre-AI years.
|
||||
- Day 7: Grid puzzle that required non-naiv implementation for part 2. Took
|
||||
me a second to realize that I could not save the coordinates by list but
|
||||
had to use a count to be efficient.
|
||||
- Day 8:
|
||||
|
||||
- Day 1: 29:00 That was emberassingly slow. Out of my rhythm?
|
||||
- Day 2: 13:24 Getting back into it but still slow af, obviously.
|
||||
- Day 3: 11:20 Ugly and slow.
|
||||
- Day 4: 21:05 -__-
|
||||
- Day 5: 29:35 -___-
|
||||
- Day 6: 4:20 okay
|
||||
- Day 7: 31:20 hmm
|
||||
- Day 8: 14:50 meh
|
||||
- Day 9: 26:00 okay
|
||||
- Day 10: 23:07 okay
|
||||
- Day 11: 75:00 -__-
|
||||
- Day 12: 10:05 okay
|
||||
- Day 13: 9:43 okayish
|
||||
- Day 14: 120:00 struggled with this one (example incorrect?)
|
||||
- Day 15: Trial and error. Should use CRT instead.
|
||||
- Day 16: 14:11
|
||||
- Day 17: 45:00 didn't follow instructions... focus!
|
||||
- Day 18: 9:43
|
||||
- Day 19: 90:00 that wasn't easy for me
|
||||
- Day 20: 67:00
|
||||
- Day 21: 32:33
|
||||
- Day 22: 90:00
|
||||
- Day 23: 60:00
|
||||
- Day 24: 48:00
|
||||
- Day 25: 6:45
|
||||
|
||||
## AoC 2017
|
||||
## AoC 2024
|
||||
|
||||
- Day 1: 7:30
|
||||
- Day 2: 6:15
|
||||
- Day 3: 75:00 hmm should have been way quicker
|
||||
- Day 4: 3:02
|
||||
- Day 5: 6:13
|
||||
- Day 6: 8:37
|
||||
- Day 7: 19:22
|
||||
- Day 8: 8:15
|
||||
- Day 9: 6:10
|
||||
- Day 10: 55:00
|
||||
- Day 11: 66:06
|
||||
- Day 12: 6:44
|
||||
- Day 13: 120:00
|
||||
- Day 14: 17:48
|
||||
- Day 15: 11:40
|
||||
- Day 16: 13:16
|
||||
- Day 17: 55:00
|
||||
- Day 18: 23:00
|
||||
- Day 19: 45:00
|
||||
- Day 20: 9:55 (would have been 10th)
|
||||
- Day 21: 90:00
|
||||
- Day 22: 25:00
|
||||
- Day 23: Multiple days... but super fun.
|
||||
- Day 24: 15:45 (48th)
|
||||
- Day 25: 41:00
|
||||
|
||||
## AoC 2018
|
||||
|
||||
- Day 1: 1:49 (2nd)
|
||||
- Day 2: 10:53
|
||||
- Day 3: 6:16 (24th)
|
||||
- Day 4: 25:16
|
||||
- Day 5: 17:03
|
||||
- Day 6: 1:10:29
|
||||
- Day 7: 20:15
|
||||
- Day 8: 18:35
|
||||
- Day 9: 1:17:52
|
||||
- Day 10: 19:14
|
||||
- Day 11: 22:52
|
||||
- Day 12: 180:00
|
||||
- Day 13: 73:09
|
||||
- Day 14: 20:48
|
||||
- Day 15: 185:11
|
||||
- Day 16: 36:10
|
||||
- Day 17: 180:00
|
||||
- Day 18: 24:04
|
||||
- Day 19: days, super fun, but hard for me
|
||||
- Day 20:
|
||||
- Day 21: 28:40 (16th - brute force but still not so bad)
|
||||
- Day 22: 185:00 (should not have been so slow but was fun)
|
||||
- Day 23:
|
||||
|
||||
## AoC 2019
|
||||
|
||||
- Day 1: 4:25 (copy and paste error, no leaderboard)
|
||||
- Day 2: 7:07 (19th)
|
||||
- Day 3: 10:46 (37th)
|
||||
- Day 4: 8:48 (just too slow, no leaderboard)
|
||||
- Day 5: 34:24 (that wasn't hard at all)
|
||||
- Day 6: 23:17 (so slow, no leaderboard)
|
||||
- Day 7: 72:17 (I found that one challenging, 30:33 would have been required for leaderboard)
|
||||
- Day 8: 08:55 (54th)
|
||||
- Day 9: 115:00 (Try to read next time.)
|
||||
- Day 10: 76:00 (This wasn't easy for me. Fun, though.)
|
||||
- Day 11: 21:04 (Too slow, but fun.)
|
||||
- Day 12: days (Took me a while to get the right idea.)
|
||||
- Day 13: >120:00 (Just struggling so much for some reason.)
|
||||
- Day 14: >120:00 (Hmm, wasn't that hard either.)
|
||||
- Day 15: >120:00 (I am really weak at the moment.)
|
||||
- Day 16: days (Wow. Just too hard for me to solve quickly?)
|
||||
- Day 17: days (Fun but too tricky for me to be fast.)
|
||||
- Day 18: days (Slow and slow algorithm.)
|
||||
- Day 19: 40:00 (Way too slow! Oversight error. Come on.)
|
||||
- Day 20: days (Not actually that hard but I struggled for no reason.)
|
||||
- Day 21: days (But it was super fun!)
|
||||
- Day 22:
|
||||
|
||||
## AoC 2020
|
||||
|
||||
- Day 1: 2:48 (people weren't able to submit because of a website outage)
|
||||
- Day 2: 4:47 (no leaderboard, you can tell it's getting faster)
|
||||
- Day 3: 7:06 (way too slow, lol; time to take it seriously)
|
||||
- Day 4: 14:30 (yo, I am just too slow)
|
||||
- Day 5: 11:53 (not competitive)
|
||||
- Day 6: 4:11 (75th, finally on the leaderboard)
|
||||
- Day 7: 24:39 (bad)
|
||||
- Day 8: 6:26 (43th, tied George Hotz :)
|
||||
- Day 9: 7:37 (choked bad)
|
||||
- Day 10: 34:27 (so weak)
|
||||
- Day 11: 21:05 (hmmm, I rally have to analyze why I am so slow)
|
||||
- Day 12: 21:52 (just slow again for an easy problem)
|
||||
- Day 13: 18:00 (I don't really understand the CRT to be honest)
|
||||
- Day 14: 40:26 (Made a bunch of mistakes even misunderstanding Python)
|
||||
- Day 15: 17:57 (Too slow for an easy one like this)
|
||||
- Day 16: 33:00 (Not too unhappy really.)
|
||||
- Day 17: 10:00 (40th)
|
||||
- Day 18: 80:00 (I am struggling with stuff where parsing is involved)
|
||||
- Day 19: 78:00 (Should have been faster)
|
||||
- Day 20: 95:00 (Lot's of code)
|
||||
- Day 21: 23:02 (Simply to slow)
|
||||
- Day 22: 45:49 (Simple and too slow)
|
||||
- Day 23: 105:00 (Sad)
|
||||
- Day 24: 15:38 (Close to leaderboard)
|
||||
- Day 25: 14:40 (Way too slow)
|
||||
|
||||
## AoC 2022
|
||||
|
||||
Done with this. Overall everything is solvable. It's more about consistency
|
||||
and focus. Of course, learning more algorithms and techniques helps.
|
||||
|
||||
**Times:**
|
||||
|
||||
- Day 1: 7:52 ... so slow brah :/ top 100 required 2:05...
|
||||
- Day 2: 22:30 ... I mistyped the first and second was just bad top 100 would
|
||||
have been 6:16 (doable?)
|
||||
- Day 3: 13:08 actually decent but top 100 required 5:24
|
||||
- Day 4: 7:08 but top 100 required 3:33 still okay
|
||||
- Day 5: 11:56 but 7:58 for top 100... getting better?
|
||||
- Day 6: 3:50 but 2:25 for leaderboard :D
|
||||
- Day 7: 27:55 and 14:47 for leaderboard; okay, I would say
|
||||
- Day 8: 61:00 and 10:00 for leaderboard; I need template code for searching
|
||||
coordinate systems
|
||||
- Day 9: 58:00 and 7:32 for leaderboard; I need code for 2D stuff
|
||||
- Day 10: 25:20 and 12:17 for leaderboard; okay, okay
|
||||
- Day 11: 45:00 and 18:00 for leaderboard; arg, it was doable man
|
||||
- Day 12: 39:45 and 9:46 for leaderboard; the people are ready for their
|
||||
searches :D
|
||||
- Day 13: 44:00 and 12:56 for leaderboard; these people are just good,
|
||||
seriously
|
||||
- Day 14: 35:00 and 14:54; I just have to get that much quicker... 2D code!
|
||||
- Day 15: 150:00 and 27:00; I didn't use Manhattan dist initially, lot's of
|
||||
debugging
|
||||
- Day 16: 52:00 and 64:00; ARE YOU SAYING I WOULD HAVE MADE THE
|
||||
LEADERBOARD?!?!?!?!?!?!?!
|
||||
- Day 17: Second one was fun with having to detect the repetition.
|
||||
- Day 18: 12:00 and 32:00; really straightforward and of course way too slow.
|
||||
- Day 19: Slow. (2024-09-13 improved with help from hyper neutrino.)
|
||||
- Day 20: Struggled way too much.
|
||||
- Day 21: Straightforward and relatively fast.
|
||||
- Day 22: Very hard and wasn't able to do hands free. Even the best guys took
|
||||
over an hour.
|
||||
- Day 23: Super straightforward, but took me way longer than it should have
|
||||
because I loose focus and make silly errors. Like back in Middleschool.
|
||||
- Day 24: Easy. Should have been faster, but no crazy mistakes. Still way too
|
||||
slow for something easy like this.
|
||||
- Day 25: Quickly solved via constraint solver. Actual solution much simpler. Don't know
|
||||
if I would have been able to figure it out.
|
||||
- Day 1: `00:01:30 124 0 00:02:49 141 0`
|
||||
- Day 2: `00:05:34 686 0 00:08:21 531 0`
|
||||
- Day 3: `00:01:48 165 0 00:02:40 56 45`
|
||||
- Day 4: `00:07:47 1101 0 00:24:07 2410 0`
|
||||
- Day 5: `00:07:07 679 0 00:23:02 1998 0`
|
||||
- Day 6: `00:09:43 1082 0 00:16:29 464 0`
|
||||
- Day 7: `00:12:29 2058 0 00:13:08 1170 0`
|
||||
- Day 8: `00:15:59 1742 0 00:26:56 2190 0`
|
||||
- Day 9: `00:48:23 6055 0 01:09:38 3159 0`
|
||||
- Day 10: `00:19:47 2976 0 00:20:47 2244 0`
|
||||
- Day 11: `00:05:13 642 0 00:33:07 2673 0`
|
||||
- Day 12: `00:30:30 3540 0 11:41:33 16212 0`
|
||||
- Day 13: `00:05:35 225 0 00:55:48 2544 0`
|
||||
- Day 14: `00:20:41 2136 0 00:35:14 1048 0`
|
||||
- Day 15: ` >24h 32248 0 >24h 23671 0`
|
||||
- Day 16: ` >24h 24941 0 >24h 20575 0`
|
||||
- Day 17: `17:34:16 23722 0 >24h 17778 0`
|
||||
- Day 18: `14:35:01 20398 0 14:37:18 19550 0`
|
||||
- Day 19: `00:14:37 2001 0 00:19:43 1584 0`
|
||||
- Day 20: `01:08:53 3637 0 01:53:01 2837 0`
|
||||
- Day 21: `00:48:40 215 0 >24h 16427 0`
|
||||
- Day 22: `00:13:04 1930 0 00:28:29 739 0`
|
||||
- Day 23: ` >24h 20096 0 >24h 17620 0`
|
||||
- Day 24: `15:57:01 17307 0 >24h 11326 0`
|
||||
- Day 25: `10:41:54 14140 0 >24h 13631 0`
|
||||
|
||||
|
||||
## AoC 2023
|
||||
@@ -255,3 +104,250 @@ and focus. Of course, learning more algorithms and techniques helps.
|
||||
nodes. I should probably try to write an algorith that does that, but meh.
|
||||
Manually plotting requires matplotlib and networkx packages.
|
||||
|
||||
|
||||
## AoC 2022
|
||||
|
||||
Done with this. Overall everything is solvable. It's more about consistency
|
||||
and focus. Of course, learning more algorithms and techniques helps.
|
||||
|
||||
**Times:**
|
||||
|
||||
- Day 1: 7:52 ... so slow brah :/ top 100 required 2:05...
|
||||
- Day 2: 22:30 ... I mistyped the first and second was just bad top 100 would
|
||||
have been 6:16 (doable?)
|
||||
- Day 3: 13:08 actually decent but top 100 required 5:24
|
||||
- Day 4: 7:08 but top 100 required 3:33 still okay
|
||||
- Day 5: 11:56 but 7:58 for top 100... getting better?
|
||||
- Day 6: 3:50 but 2:25 for leaderboard :D
|
||||
- Day 7: 27:55 and 14:47 for leaderboard; okay, I would say
|
||||
- Day 8: 61:00 and 10:00 for leaderboard; I need template code for searching
|
||||
coordinate systems
|
||||
- Day 9: 58:00 and 7:32 for leaderboard; I need code for 2D stuff
|
||||
- Day 10: 25:20 and 12:17 for leaderboard; okay, okay
|
||||
- Day 11: 45:00 and 18:00 for leaderboard; arg, it was doable man
|
||||
- Day 12: 39:45 and 9:46 for leaderboard; the people are ready for their
|
||||
searches :D
|
||||
- Day 13: 44:00 and 12:56 for leaderboard; these people are just good,
|
||||
seriously
|
||||
- Day 14: 35:00 and 14:54; I just have to get that much quicker... 2D code!
|
||||
- Day 15: 150:00 and 27:00; I didn't use Manhattan dist initially, lot's of
|
||||
debugging
|
||||
- Day 16: 52:00 and 64:00; ARE YOU SAYING I WOULD HAVE MADE THE
|
||||
LEADERBOARD?!?!?!?!?!?!?!
|
||||
- Day 17: Second one was fun with having to detect the repetition.
|
||||
- Day 18: 12:00 and 32:00; really straightforward and of course way too slow.
|
||||
- Day 19: Slow. (2024-09-13 improved with help from hyper neutrino.)
|
||||
- Day 20: Struggled way too much.
|
||||
- Day 21: Straightforward and relatively fast.
|
||||
- Day 22: Very hard and wasn't able to do hands free. Even the best guys took
|
||||
over an hour.
|
||||
- Day 23: Super straightforward, but took me way longer than it should have
|
||||
because I loose focus and make silly errors. Like back in Middleschool.
|
||||
- Day 24: Easy. Should have been faster, but no crazy mistakes. Still way too
|
||||
slow for something easy like this.
|
||||
- Day 25: Quickly solved via constraint solver. Actual solution much simpler. Don't know
|
||||
if I would have been able to figure it out.
|
||||
|
||||
|
||||
## AoC 2021
|
||||
|
||||
- Day 1: 4:01 (Haha. Why am I so bad?!?!)
|
||||
- Day 2: 6:36 (Okay. I might as well stop doing these.)
|
||||
- Day 3: 23:46 (How long can I take when I try to take long?)
|
||||
- Day 4: 22:18 (No way. Such stupid bugs.)
|
||||
- Day 5: 13:30 (Decent, but obviously too slow.)
|
||||
- Day 6: 16:33 (Right idea quickly but then struggled to implement.)
|
||||
- Day 7: 6:12 (Decent, but too slow again, obviously. Maybe fast enough for part 1.)
|
||||
- Day 8: 72:00 (Yeah, that was too much of a struggle.)
|
||||
- Day 9: 8:07 (37th okay, okay)
|
||||
- Day 10: 15:50 (I was pretty happy with that but double the 100th place.)
|
||||
- Day 11: 11:43 (Could have been much faster here.)
|
||||
- Day 12: 19:11 (Okayish, but of course too slow for leaderboard.)
|
||||
- Day 13: 16:48 (Should have been way faster.)
|
||||
- Day 14: 25:52 (Not hard but just too slow.)
|
||||
- Day 15: 19:17 (Not that bad. Multiplying the thing threw me off.)
|
||||
- Day 16: 50:01 (Way too slow. Was non-trivial but fun. Much better was feasible.)
|
||||
- Day 17: 21:59 (Not tricky again but struggling for no reason.)
|
||||
- Day 18: 162:00 (I couldn't figure out how to solve it as a tree so I did the basic way.)
|
||||
- Day 19: days (Super hard for me but super fun ultimately once I had the right approach.)
|
||||
- Day 20: 105:00 (That wasn't easy but was able to solve in one go.)
|
||||
- Day 21: 37:45 (Wasn't hard but I was just too slow.)
|
||||
- Day 22: 142:00 (Wonderful problem and hard for me but learned something new for sure.)
|
||||
- Day 23: 81:38 (Fun but obviously not competitive.)
|
||||
- Day 24: 232:00 (Super hard for me but I am proud of it.)
|
||||
- Day 25: 15:43 (That could have been much much faster.)
|
||||
|
||||
|
||||
## AoC 2020
|
||||
|
||||
- Day 1: 2:48 (people weren't able to submit because of a website outage)
|
||||
- Day 2: 4:47 (no leaderboard, you can tell it's getting faster)
|
||||
- Day 3: 7:06 (way too slow, lol; time to take it seriously)
|
||||
- Day 4: 14:30 (yo, I am just too slow)
|
||||
- Day 5: 11:53 (not competitive)
|
||||
- Day 6: 4:11 (75th, finally on the leaderboard)
|
||||
- Day 7: 24:39 (bad)
|
||||
- Day 8: 6:26 (43th, tied George Hotz :)
|
||||
- Day 9: 7:37 (choked bad)
|
||||
- Day 10: 34:27 (so weak)
|
||||
- Day 11: 21:05 (hmmm, I rally have to analyze why I am so slow)
|
||||
- Day 12: 21:52 (just slow again for an easy problem)
|
||||
- Day 13: 18:00 (I don't really understand the CRT to be honest)
|
||||
- Day 14: 40:26 (Made a bunch of mistakes even misunderstanding Python)
|
||||
- Day 15: 17:57 (Too slow for an easy one like this)
|
||||
- Day 16: 33:00 (Not too unhappy really.)
|
||||
- Day 17: 10:00 (40th)
|
||||
- Day 18: 80:00 (I am struggling with stuff where parsing is involved)
|
||||
- Day 19: 78:00 (Should have been faster)
|
||||
- Day 20: 95:00 (Lot's of code)
|
||||
- Day 21: 23:02 (Simply to slow)
|
||||
- Day 22: 45:49 (Simple and too slow)
|
||||
- Day 23: 105:00 (Sad)
|
||||
- Day 24: 15:38 (Close to leaderboard)
|
||||
- Day 25: 14:40 (Way too slow)
|
||||
|
||||
|
||||
## AoC 2019
|
||||
|
||||
- Day 1: 4:25 (copy and paste error, no leaderboard)
|
||||
- Day 2: 7:07 (19th)
|
||||
- Day 3: 10:46 (37th)
|
||||
- Day 4: 8:48 (just too slow, no leaderboard)
|
||||
- Day 5: 34:24 (that wasn't hard at all)
|
||||
- Day 6: 23:17 (so slow, no leaderboard)
|
||||
- Day 7: 72:17 (I found that one challenging, 30:33 would have been required for leaderboard)
|
||||
- Day 8: 08:55 (54th)
|
||||
- Day 9: 115:00 (Try to read next time.)
|
||||
- Day 10: 76:00 (This wasn't easy for me. Fun, though.)
|
||||
- Day 11: 21:04 (Too slow, but fun.)
|
||||
- Day 12: days (Took me a while to get the right idea.)
|
||||
- Day 13: >120:00 (Just struggling so much for some reason.)
|
||||
- Day 14: >120:00 (Hmm, wasn't that hard either.)
|
||||
- Day 15: >120:00 (I am really weak at the moment.)
|
||||
- Day 16: days (Wow. Just too hard for me to solve quickly?)
|
||||
- Day 17: days (Fun but too tricky for me to be fast.)
|
||||
- Day 18: days (Slow and slow algorithm.)
|
||||
- Day 19: 40:00 (Way too slow! Oversight error. Come on.)
|
||||
- Day 20: days (Not actually that hard but I struggled for no reason.)
|
||||
- Day 21: days (But it was super fun!)
|
||||
- Day 22: days (Needed some help...)
|
||||
- Day 23: 23:13 (Still too slow even though my int computer was in good shape...)
|
||||
- Day 24: 53:00 (Can I ever even get points at all?)
|
||||
- Day 25: 70:00 (Well, done at least. Super super fun!)
|
||||
|
||||
|
||||
## AoC 2018
|
||||
|
||||
- Day 1: 1:49 (2nd)
|
||||
- Day 2: 10:53
|
||||
- Day 3: 6:16 (24th)
|
||||
- Day 4: 25:16
|
||||
- Day 5: 17:03
|
||||
- Day 6: 1:10:29
|
||||
- Day 7: 20:15
|
||||
- Day 8: 18:35
|
||||
- Day 9: 1:17:52
|
||||
- Day 10: 19:14
|
||||
- Day 11: 22:52
|
||||
- Day 12: 180:00
|
||||
- Day 13: 73:09
|
||||
- Day 14: 20:48
|
||||
- Day 15: 185:11
|
||||
- Day 16: 36:10
|
||||
- Day 17: 180:00
|
||||
- Day 18: 24:04
|
||||
- Day 19: days (super fun, but hard for me)
|
||||
- Day 20: weeks (a cache was all it took - weak)
|
||||
- Day 21: 28:40 (16th - brute force but still not so bad)
|
||||
- Day 22: 185:00 (should not have been so slow but was fun)
|
||||
- Day 23: days
|
||||
- Day 24: days
|
||||
- Day 25: 29:20 (slow)
|
||||
|
||||
|
||||
## AoC 2017
|
||||
|
||||
- Day 1: 7:30
|
||||
- Day 2: 6:15
|
||||
- Day 3: 75:00 hmm should have been way quicker
|
||||
- Day 4: 3:02
|
||||
- Day 5: 6:13
|
||||
- Day 6: 8:37
|
||||
- Day 7: 19:22
|
||||
- Day 8: 8:15
|
||||
- Day 9: 6:10
|
||||
- Day 10: 55:00
|
||||
- Day 11: 66:06
|
||||
- Day 12: 6:44
|
||||
- Day 13: 120:00
|
||||
- Day 14: 17:48
|
||||
- Day 15: 11:40
|
||||
- Day 16: 13:16
|
||||
- Day 17: 55:00
|
||||
- Day 18: 23:00
|
||||
- Day 19: 45:00
|
||||
- Day 20: 9:55 (would have been 10th)
|
||||
- Day 21: 90:00
|
||||
- Day 22: 25:00
|
||||
- Day 23: Multiple days... but super fun.
|
||||
- Day 24: 15:45 (48th)
|
||||
- Day 25: 41:00
|
||||
|
||||
|
||||
## AoC 2016
|
||||
|
||||
- Day 1: 29:00 That was emberassingly slow. Out of my rhythm?
|
||||
- Day 2: 13:24 Getting back into it but still slow af, obviously.
|
||||
- Day 3: 11:20 Ugly and slow.
|
||||
- Day 4: 21:05 -__-
|
||||
- Day 5: 29:35 -___-
|
||||
- Day 6: 4:20 okay
|
||||
- Day 7: 31:20 hmm
|
||||
- Day 8: 14:50 meh
|
||||
- Day 9: 26:00 okay
|
||||
- Day 10: 23:07 okay
|
||||
- Day 11: 75:00 -__-
|
||||
- Day 12: 10:05 okay
|
||||
- Day 13: 9:43 okayish
|
||||
- Day 14: 120:00 struggled with this one (example incorrect?)
|
||||
- Day 15: Trial and error. Should use CRT instead.
|
||||
- Day 16: 14:11
|
||||
- Day 17: 45:00 didn't follow instructions... focus!
|
||||
- Day 18: 9:43
|
||||
- Day 19: 90:00 that wasn't easy for me
|
||||
- Day 20: 67:00
|
||||
- Day 21: 32:33
|
||||
- Day 22: 90:00
|
||||
- Day 23: 60:00
|
||||
- Day 24: 48:00
|
||||
- Day 25: 6:45
|
||||
|
||||
|
||||
## AoC 2015
|
||||
|
||||
- Day 1: 3:10
|
||||
- Day 2: 5:24 :/
|
||||
- Day 3: 7:26 ... :/
|
||||
- Day 4: 5:11 ...
|
||||
- Day 5: 14:05 o.O
|
||||
- Day 6: 15:00
|
||||
- Day 7: 17:00
|
||||
- Day 8: 14:55
|
||||
- Day 9: 13:48
|
||||
- Day 10: 70:00 ... slow, but fun
|
||||
- Day 11: 12:30
|
||||
- Day 12: 6:03
|
||||
- Day 13: 7:06 (would have been first by a minute, probably 70ish in 2023)
|
||||
- Day 14: 27:45 ... that was weak, logic error for part 2 :/
|
||||
- Day 15: 16:00 I should probably stop brute forcing these optimization problems
|
||||
- Day 16: 19:00
|
||||
- Day 17: 9:05
|
||||
- Day 18: 10:39
|
||||
- Day 19: Many days... yeah this one took me way too long to figure out
|
||||
- Day 20: 10:54
|
||||
- Day 21: 25:52 cute bug where I didn't consider that no armor is an option
|
||||
- Day 22: That was bad. Did not know how to choose between dfs/bfs and logic errors.
|
||||
- Day 23: 10:00
|
||||
- Day 24: 20:00 ugly - recursive solution would be more elegant
|
||||
- Day 25: 9:34
|
||||
|
||||
|
||||
39
lib.py
39
lib.py
@@ -59,6 +59,10 @@ class Grid2D:
|
||||
def hash(self):
|
||||
return tuple(map(lambda row: tuple(row), self.grid))
|
||||
|
||||
def clone(self):
|
||||
from copy import deepcopy
|
||||
return deepcopy(self)
|
||||
|
||||
def clone_with_val(self, val):
|
||||
c = Grid2D("d\nd")
|
||||
c.n_rows = self.n_rows
|
||||
@@ -226,7 +230,7 @@ def count_trailing_repeats(lst):
|
||||
class A_Star(object):
|
||||
def __init__(self, starts, is_goal, h, d, neighbors):
|
||||
"""
|
||||
:param h: heuristic function
|
||||
:param h: heuristic function (never overestimate)
|
||||
:param d: cost from node to node function
|
||||
:param neighbors: neighbors function
|
||||
"""
|
||||
@@ -272,7 +276,7 @@ def extract_year_and_date(scriptname) -> tuple[str, str]:
|
||||
def get_data(filename):
|
||||
path, file = os.path.split(filename)
|
||||
year = path[-4:]
|
||||
day = file.replace("d", "").replace(".py", "")
|
||||
day = file.replace("d0", "").replace("d", "").replace(".py", "")
|
||||
txt_file = f"d{day}.txt"
|
||||
|
||||
if os.path.isfile(txt_file):
|
||||
@@ -297,6 +301,35 @@ def mod_inverse(a, m):
|
||||
|
||||
g, x, _ = egcd(a, m)
|
||||
if g != 1:
|
||||
raise Exception('Modular inverse does not exist')
|
||||
raise Exception("Modular inverse does not exist")
|
||||
else:
|
||||
return x % m
|
||||
|
||||
|
||||
class V:
|
||||
def __init__(self, *args):
|
||||
self.xs: tuple[int] = tuple(args)
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, V):
|
||||
return all(v1 == v2 for v1, v2 in zip(self.xs, other.xs))
|
||||
elif hasattr(other, "__len__") and len(self.xs) == len(other):
|
||||
return all(v1 == v2 for v1, v2 in zip(self.xs, other))
|
||||
return False
|
||||
|
||||
def __getitem__(self, i: int):
|
||||
return self.xs[i]
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.xs)
|
||||
|
||||
def __add__(self, other):
|
||||
if isinstance(other, V):
|
||||
return V(*[v1 + v2 for v1, v2 in zip(self.xs, other.xs)])
|
||||
assert hasattr(other, "__len__"), f"V.__add__({self}, {other}) missing `len`"
|
||||
assert len(self.xs) == len(other), f"V.__add__({self}, {other}) `len` mismatch"
|
||||
return V(*[v1 + v2 for v1, v2 in zip(self.xs, other)])
|
||||
|
||||
def __repr__(self):
|
||||
s = ", ".join(map(str, self.xs))
|
||||
return f"V({s})"
|
||||
|
||||
10
pyproject.toml
Normal file
10
pyproject.toml
Normal file
@@ -0,0 +1,10 @@
|
||||
[project]
|
||||
name = "aocpy"
|
||||
version = "0.1.0"
|
||||
description = "Make ruff and ty available via uv"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = [
|
||||
"ruff>=0.14.11",
|
||||
"ty>=0.0.11",
|
||||
]
|
||||
Reference in New Issue
Block a user