Compare commits
84 Commits
631205086d
...
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 | |||
| 7d1dc3f95e | |||
| ea28a17ab9 | |||
| 6475681cfb | |||
| 97b05614ee | |||
| 36f20610ae | |||
| 695a4f7ddb | |||
| 25443fba48 | |||
| bb9e3321d7 | |||
| 2eba36a29a | |||
| 25bd810886 | |||
| 6c3aec3d6e | |||
| db383c7cfa | |||
| 7986ecfc21 | |||
| 0f7606410c | |||
| 6e588bb928 | |||
| 4a72eeb348 | |||
| 9cd17279d9 | |||
| bf88f13791 | |||
| fa6ea1dfbe | |||
| 6cea472035 | |||
| ad53396ca6 | |||
| b307e09f2b | |||
| 381f186a59 | |||
| 5fae7c0366 | |||
| 2647ccd353 | |||
| 9a30c8b88d | |||
| 305fe0b325 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -4,6 +4,8 @@
|
|||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
*$py.class
|
*$py.class
|
||||||
|
.python-version
|
||||||
|
uv.lock
|
||||||
|
|
||||||
# C extensions
|
# C extensions
|
||||||
*.so
|
*.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
|
floor = 0
|
||||||
for i, c in enumerate(data):
|
for i, c in enumerate(data):
|
||||||
@@ -10,13 +11,6 @@ for i, c in enumerate(data):
|
|||||||
floor += 1
|
floor += 1
|
||||||
elif c == ")":
|
elif c == ")":
|
||||||
floor -= 1
|
floor -= 1
|
||||||
else:
|
if floor == -1:
|
||||||
print(c)
|
|
||||||
assert False
|
|
||||||
|
|
||||||
if part_2 and floor == -1:
|
|
||||||
print(i + 1)
|
print(i + 1)
|
||||||
break
|
break
|
||||||
|
|
||||||
if not part_2:
|
|
||||||
print(floor)
|
|
||||||
|
|||||||
54
2015/d10.py
54
2015/d10.py
@@ -1,35 +1,29 @@
|
|||||||
from functools import lru_cache
|
from lib import get_data
|
||||||
data = open(0).read().strip()
|
|
||||||
|
|
||||||
part_1 = False
|
data = get_data(__file__)
|
||||||
if part_1:
|
|
||||||
repeats = 40
|
|
||||||
else:
|
|
||||||
repeats = 50
|
|
||||||
|
|
||||||
@lru_cache
|
|
||||||
def look_and_say(data: str) -> str:
|
def iter(xs):
|
||||||
r = ""
|
if not xs:
|
||||||
i = 0
|
return []
|
||||||
while i < len(data):
|
grouped = []
|
||||||
count = 0
|
current_element = xs[0]
|
||||||
c = data[i]
|
count = 1
|
||||||
while i < len(data) and data[i] == c:
|
for element in xs[1:]:
|
||||||
|
if element == current_element:
|
||||||
count += 1
|
count += 1
|
||||||
i += 1
|
else:
|
||||||
r += f"{count}{c}"
|
grouped.append(count)
|
||||||
return r
|
grouped.append(current_element)
|
||||||
|
current_element = element
|
||||||
|
count = 1
|
||||||
|
grouped.append(count)
|
||||||
|
grouped.append(current_element)
|
||||||
|
return grouped
|
||||||
|
|
||||||
CHUNK_SIZE = 10000
|
|
||||||
for _ in range(repeats):
|
|
||||||
ndata = ""
|
|
||||||
lo, up = 0, CHUNK_SIZE
|
|
||||||
while up < len(data):
|
|
||||||
while up < len(data) and data[up - 1] == data[up]:
|
|
||||||
up += 1
|
|
||||||
ndata += look_and_say(data[lo:up])
|
|
||||||
lo, up = up, up + CHUNK_SIZE
|
|
||||||
ndata += look_and_say(data[lo:up])
|
|
||||||
data = ndata
|
|
||||||
|
|
||||||
print(len(data))
|
for repeat in [40, 50]:
|
||||||
|
xs = list(map(int, list(data.strip())))
|
||||||
|
for _ in range(repeat):
|
||||||
|
xs = iter(xs)
|
||||||
|
print(len(xs))
|
||||||
|
|||||||
17
2015/d11.py
17
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):
|
def is_valid(pw):
|
||||||
# Passwords must include one increasing straight of at least three letters,
|
# Passwords must include one increasing straight of at least three letters,
|
||||||
@@ -26,24 +29,26 @@ def is_valid(pw):
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def inc(pw):
|
def inc(pw):
|
||||||
pw = list(map(ord, pw))
|
pw = list(map(ord, pw))
|
||||||
for i in range(len(pw) - 1, -1, -1):
|
for i in range(len(pw) - 1, -1, -1):
|
||||||
pw[i] += 1
|
pw[i] += 1
|
||||||
if pw[i] == ord('z') + 1:
|
if pw[i] == ord("z") + 1:
|
||||||
pw[i] = ord('a')
|
pw[i] = ord("a")
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
return "".join(map(chr, pw))
|
return "".join(map(chr, pw))
|
||||||
|
|
||||||
part_1 = False
|
|
||||||
|
part_1 = True
|
||||||
valid_count = 0
|
valid_count = 0
|
||||||
while True:
|
while True:
|
||||||
data = inc(data)
|
data = inc(data)
|
||||||
if is_valid(data):
|
if is_valid(data):
|
||||||
valid_count += 1
|
valid_count += 1
|
||||||
if part_1 and valid_count == 1:
|
if part_1 and valid_count == 1:
|
||||||
break
|
print(data)
|
||||||
elif valid_count == 2:
|
elif valid_count == 2:
|
||||||
|
print(data)
|
||||||
break
|
break
|
||||||
print(data)
|
|
||||||
|
|||||||
54
2015/d12.py
54
2015/d12.py
@@ -1,27 +1,37 @@
|
|||||||
|
from lib import get_data
|
||||||
import json
|
import json
|
||||||
|
|
||||||
data = open(0).read().strip()
|
data = get_data(__file__).strip()
|
||||||
part_2 = False
|
|
||||||
|
|
||||||
def addup(d):
|
o = json.loads(data)
|
||||||
r = 0
|
|
||||||
if isinstance(d, list):
|
|
||||||
for e in d:
|
def xsum(obj, part_2=False):
|
||||||
v = addup(e)
|
t = 0
|
||||||
if v is not None:
|
if type(obj) is int:
|
||||||
r += v
|
t += obj
|
||||||
elif isinstance(d, dict):
|
elif type(obj) is list:
|
||||||
for e in d.values():
|
for o in obj:
|
||||||
v = addup(e)
|
t += xsum(o, part_2)
|
||||||
if v is None:
|
elif type(obj) is dict:
|
||||||
return 0
|
if part_2:
|
||||||
r += v
|
st = 0
|
||||||
elif isinstance(d, str):
|
for o in obj.values():
|
||||||
if part_2 and d == "red":
|
st += xsum(o, part_2)
|
||||||
return None
|
if type(o) is str and o == "red":
|
||||||
|
break
|
||||||
else:
|
else:
|
||||||
r += d
|
t += st
|
||||||
return r
|
else:
|
||||||
|
for o in obj.values():
|
||||||
|
t += xsum(o, part_2)
|
||||||
|
elif type(obj) is str:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
print(obj)
|
||||||
|
assert False
|
||||||
|
return t
|
||||||
|
|
||||||
data = json.loads(data)
|
|
||||||
print(addup(data))
|
print(xsum(o))
|
||||||
|
print(xsum(o, True))
|
||||||
|
|||||||
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
|
from itertools import permutations
|
||||||
data = open(0).read().strip()
|
|
||||||
|
|
||||||
part_2 = False
|
data = get_data(__file__).strip()
|
||||||
|
|
||||||
|
pairs = defaultdict(int)
|
||||||
people = set()
|
people = set()
|
||||||
scores = {}
|
|
||||||
for line in data.splitlines():
|
for line in data.splitlines():
|
||||||
a, _, wl, score, _, _, _, _, _, _, b = line.split()
|
a, _, gainlose, amount, _, _, _, _, _, _, b = line[:-1].split()
|
||||||
b = b.replace(".", "")
|
amount = int(amount)
|
||||||
score = int(score)
|
if gainlose == "lose":
|
||||||
if wl == "lose":
|
amount = -amount
|
||||||
score = -score
|
pairs[(a, b)] = amount
|
||||||
people.add(a)
|
people.add(a)
|
||||||
people.add(b)
|
|
||||||
scores[(a, b)] = score
|
|
||||||
if part_2:
|
|
||||||
scores[(a, "me")] = 0
|
|
||||||
scores[("me", a)] = 0
|
|
||||||
scores[(b, "me")] = 0
|
|
||||||
scores[("me", b)] = 0
|
|
||||||
|
|
||||||
if part_2:
|
|
||||||
people.add("me")
|
|
||||||
|
|
||||||
max_score = 0
|
def calc_gain(people):
|
||||||
for p in permutations(list(people)):
|
maxgain = 0
|
||||||
s = 0
|
for xs in permutations(list(people)):
|
||||||
for i in range(len(p)):
|
gain = 0
|
||||||
a, b = p[i], p[(i + 1) % len(p)]
|
for i in range(len(xs)):
|
||||||
s += scores[(a, b)]
|
gain += pairs[(xs[i], xs[(i + 1) % len(xs)])]
|
||||||
s += scores[(b, a)]
|
gain += pairs[(xs[(i + 1) % len(xs)], xs[i])]
|
||||||
max_score = max(max_score, s)
|
maxgain = max(maxgain, gain)
|
||||||
|
return maxgain
|
||||||
|
|
||||||
print(max_score)
|
|
||||||
|
print(calc_gain(people))
|
||||||
|
people.add("Felix")
|
||||||
|
print(calc_gain(people))
|
||||||
|
|||||||
79
2015/d14.py
79
2015/d14.py
@@ -1,53 +1,40 @@
|
|||||||
import lib
|
from lib import get_data
|
||||||
data = open(0).read().strip()
|
from lib import ints, fst
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
part_1 = False
|
data = get_data(__file__).strip()
|
||||||
|
deers = []
|
||||||
rds = []
|
|
||||||
max_dist = 0
|
|
||||||
gtime = 2503
|
|
||||||
rds = []
|
|
||||||
for line in data.splitlines():
|
for line in data.splitlines():
|
||||||
speed, time, rest = lib.str_to_ints(line)
|
speed, time, rest = ints(line)
|
||||||
rds.append([speed, time, rest, 0, 0, 0])
|
deers.append([0, speed, time, rest, time, 0])
|
||||||
speed *= 1000
|
|
||||||
|
|
||||||
dist = 0
|
points = defaultdict(int)
|
||||||
ctime = 0
|
for _ in range(2503):
|
||||||
while ctime < gtime:
|
max_dists = defaultdict(list)
|
||||||
t = min(time, gtime - ctime)
|
for i, deer in enumerate(deers):
|
||||||
dist += (speed * t)
|
dist, speed, time, rest, time_left, rest_left = deer
|
||||||
ctime += time
|
if time_left > 0:
|
||||||
ctime += rest
|
time_left -= 1
|
||||||
max_dist = max(dist, max_dist)
|
dist += speed
|
||||||
|
elif time_left == 0:
|
||||||
|
time_left = -1
|
||||||
|
rest_left = rest - 1
|
||||||
|
elif rest_left > 0:
|
||||||
|
rest_left -= 1
|
||||||
|
elif rest_left == 0:
|
||||||
|
time_left = time - 1
|
||||||
|
dist += speed
|
||||||
|
rest_left = -1
|
||||||
|
max_dists[dist].append(i)
|
||||||
|
|
||||||
if part_1:
|
deer[0] = dist
|
||||||
print(max_dist // 1000)
|
deer[4] = time_left
|
||||||
|
deer[5] = rest_left
|
||||||
|
|
||||||
scores = [0 for _ in range(len(rds))]
|
max_deers = max(max_dists.items())
|
||||||
for _ in range(gtime):
|
for di in max_deers[1]:
|
||||||
for i in range(len(rds)):
|
points[di] += 1
|
||||||
speed, time, rest, dist, travel_time, rest_time = rds[i]
|
|
||||||
rd = rds[i]
|
|
||||||
if travel_time < time:
|
|
||||||
rd[3] += speed
|
|
||||||
rd[4] += 1
|
|
||||||
elif rest_time < rest:
|
|
||||||
rd[5] += 1
|
|
||||||
if rd[5] == rest:
|
|
||||||
rd[4] = 0
|
|
||||||
rd[5] = 0
|
|
||||||
|
|
||||||
max_i = -1
|
|
||||||
max_v = 0
|
|
||||||
for i, rd in enumerate(rds):
|
|
||||||
if rd[3] > max_v:
|
|
||||||
max_i = i
|
|
||||||
max_v = rd[3]
|
|
||||||
for i, rd in enumerate(rds):
|
|
||||||
if rd[3] == max_v:
|
|
||||||
scores[i] += 1
|
|
||||||
|
|
||||||
if not part_1:
|
|
||||||
print(max(scores))
|
|
||||||
|
|
||||||
|
print(max(map(fst, deers)))
|
||||||
|
print(max(points.values()))
|
||||||
|
|||||||
24
2015/d2.py
24
2015/d2.py
@@ -1,19 +1,17 @@
|
|||||||
from lib import *
|
from lib import get_data, ints
|
||||||
|
|
||||||
data = open(0).readlines()
|
data = get_data(__file__)
|
||||||
|
|
||||||
part_1 = True
|
a = 0
|
||||||
if part_1:
|
for line in data.splitlines():
|
||||||
a = 0
|
l, w, h = ints(line)
|
||||||
for line in data:
|
|
||||||
l, w, h = list(map(int, line.split("x")))
|
|
||||||
slack = min([l * w, w * h, h * l])
|
slack = min([l * w, w * h, h * l])
|
||||||
a += (2*l*w + 2*w*h + 2*h*l + slack)
|
a += 2 * l * w + 2 * w * h + 2 * h * l + slack
|
||||||
else:
|
print(a)
|
||||||
a = 0
|
|
||||||
for line in data:
|
a = 0
|
||||||
l, w, h = list(map(int, line.split("x")))
|
for line in data.splitlines():
|
||||||
|
l, w, h = ints(line)
|
||||||
sd = min([2 * (l + w), 2 * (w + h), 2 * (h + l)])
|
sd = min([2 * (l + w), 2 * (w + h), 2 * (h + l)])
|
||||||
a += sd + (l * w * h)
|
a += sd + (l * w * h)
|
||||||
|
|
||||||
print(a)
|
print(a)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import lib
|
|||||||
data = int(open(0).read().strip())
|
data = int(open(0).read().strip())
|
||||||
part_1 = False
|
part_1 = False
|
||||||
|
|
||||||
|
|
||||||
def calc(n):
|
def calc(n):
|
||||||
fs = lib.prime_factors(n)
|
fs = lib.prime_factors(n)
|
||||||
vs = set([1, n])
|
vs = set([1, n])
|
||||||
@@ -21,6 +22,7 @@ def calc(n):
|
|||||||
r += e * 11
|
r += e * 11
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
for i in range(3, 10000000):
|
for i in range(3, 10000000):
|
||||||
c = calc(i)
|
c = calc(i)
|
||||||
if c >= data:
|
if c >= data:
|
||||||
|
|||||||
29
2015/d3.py
29
2015/d3.py
@@ -1,9 +1,7 @@
|
|||||||
import sys
|
from lib import get_data
|
||||||
from lib import *
|
from lib import V
|
||||||
|
|
||||||
data = open(0).read()
|
data = get_data(__file__)
|
||||||
|
|
||||||
part_1 = False
|
|
||||||
|
|
||||||
DIRS = {
|
DIRS = {
|
||||||
"^": (-1, 0),
|
"^": (-1, 0),
|
||||||
@@ -12,30 +10,27 @@ DIRS = {
|
|||||||
"<": (0, -1),
|
"<": (0, -1),
|
||||||
}
|
}
|
||||||
|
|
||||||
if part_1:
|
pos = V(0, 0)
|
||||||
pos = (0, 0)
|
poss = set([pos])
|
||||||
poss = set([pos])
|
|
||||||
|
|
||||||
for c in data:
|
for c in data:
|
||||||
d = DIRS[c]
|
d = DIRS[c]
|
||||||
pos = pos[0] + d[0], pos[1] + d[1]
|
pos = pos + d
|
||||||
poss.add(pos)
|
poss.add(pos)
|
||||||
|
|
||||||
print(len(poss))
|
print(len(poss))
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
a = (0, 0)
|
a = V(0, 0)
|
||||||
b = (0, 0)
|
b = V(0, 0)
|
||||||
poss = set([a, b])
|
poss = set([a, b])
|
||||||
for i, c in enumerate(data):
|
for i, c in enumerate(data):
|
||||||
if i % 2 == 0:
|
if i % 2 == 0:
|
||||||
d = DIRS[c]
|
d = DIRS[c]
|
||||||
a = a[0] + d[0], a[1] + d[1]
|
a = a + d
|
||||||
poss.add(a)
|
poss.add(a)
|
||||||
else:
|
else:
|
||||||
d = DIRS[c]
|
d = DIRS[c]
|
||||||
b = b[0] + d[0], b[1] + d[1]
|
b = b + d
|
||||||
poss.add(b)
|
poss.add(b)
|
||||||
|
|
||||||
print(len(poss))
|
print(len(poss))
|
||||||
sys.exit(0)
|
|
||||||
|
|||||||
15
2015/d4.py
15
2015/d4.py
@@ -1,16 +1,10 @@
|
|||||||
from lib import *
|
from lib import get_data
|
||||||
import hashlib
|
import hashlib
|
||||||
|
|
||||||
part_1 = True
|
data = get_data(__file__).strip()
|
||||||
|
|
||||||
if part_1:
|
for digits in [5, 6]:
|
||||||
digits = 5
|
for i in range(10**9):
|
||||||
else:
|
|
||||||
digits = 6
|
|
||||||
|
|
||||||
data = open(0).read().strip()
|
|
||||||
|
|
||||||
for i in range(10**9):
|
|
||||||
text = data + str(i)
|
text = data + str(i)
|
||||||
md5_hash = hashlib.md5(text.encode()).hexdigest()
|
md5_hash = hashlib.md5(text.encode()).hexdigest()
|
||||||
for c in md5_hash[:digits]:
|
for c in md5_hash[:digits]:
|
||||||
@@ -19,4 +13,3 @@ for i in range(10**9):
|
|||||||
else:
|
else:
|
||||||
print(i)
|
print(i)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|||||||
95
2015/d5.py
95
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:
|
def is_nice(line):
|
||||||
res = 0
|
# It contains at least three vowels (aeiou only), like aei, xazegov, or aeiouaeiouaeiou.
|
||||||
for line in data.splitlines():
|
|
||||||
vc = 0
|
vc = 0
|
||||||
for c in line:
|
for v in "aeiou":
|
||||||
if c in "aoeui":
|
vc += line.count(v)
|
||||||
vc += 1
|
|
||||||
if vc < 3:
|
if vc < 3:
|
||||||
continue
|
return False
|
||||||
|
|
||||||
prev = None
|
# It contains at least one letter that appears twice in a row, like xx,
|
||||||
for c in line:
|
# abcdde (dd), or aabbccdd (aa, bb, cc, or dd).
|
||||||
if c == prev:
|
for i in range(len(line) - 1):
|
||||||
break
|
if line[i] == line[i + 1]:
|
||||||
prev = c
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
|
|
||||||
contains = False
|
|
||||||
ss = ["ab", "cd", "pq", "xy"]
|
|
||||||
for s in ss:
|
|
||||||
if s in line:
|
|
||||||
contains = True
|
|
||||||
|
|
||||||
if contains:
|
|
||||||
continue
|
|
||||||
res += 1
|
|
||||||
print(res)
|
|
||||||
else:
|
|
||||||
res = 0
|
|
||||||
for line in data.splitlines():
|
|
||||||
pairs = {}
|
|
||||||
for i in range(0, len(line) - 1):
|
|
||||||
p = line[i:i+2]
|
|
||||||
if p in pairs and i > pairs[p] + 1:
|
|
||||||
break
|
|
||||||
if not p in pairs:
|
|
||||||
pairs[p] = i
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
|
|
||||||
for i in range(0, len(line) - 2):
|
|
||||||
if line[i] == line[i + 2]:
|
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
continue
|
return False
|
||||||
res += 1
|
|
||||||
print(res)
|
# It does not contain the strings ab, cd, pq, or xy, even if they are
|
||||||
|
# part of one of the other requirements.
|
||||||
|
for i in range(len(line) - 1):
|
||||||
|
cc = "".join(line[i : i + 2])
|
||||||
|
if cc in ["ab", "cd", "pq", "xy"]:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def is_nice_2(line):
|
||||||
|
good = False
|
||||||
|
for i in range(len(line) - 1):
|
||||||
|
cc = "".join(line[i : i + 2])
|
||||||
|
for j in range(i + 2, len(line) - 1):
|
||||||
|
dd = "".join(line[j : j + 2])
|
||||||
|
if cc == dd:
|
||||||
|
good = True
|
||||||
|
if not good:
|
||||||
|
return False
|
||||||
|
|
||||||
|
for i in range(len(line) - 2):
|
||||||
|
cc = "".join(line[i : i + 3])
|
||||||
|
if cc[0] == cc[2]:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
t = sum(1 if is_nice(line) else 0 for line in data.splitlines())
|
||||||
|
print(t)
|
||||||
|
|
||||||
|
|
||||||
|
t = sum(1 if is_nice_2(line) else 0 for line in data.splitlines())
|
||||||
|
print(t)
|
||||||
|
|||||||
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()
|
data = get_data(__file__)
|
||||||
|
|
||||||
part_2 = True
|
|
||||||
lights = [[0 for _ in range(1000)] for _ in range(1000)]
|
|
||||||
|
|
||||||
|
ons = set()
|
||||||
|
ons2 = DD(int)
|
||||||
for line in data.splitlines():
|
for line in data.splitlines():
|
||||||
x1, y1, x2, y2 = str_to_ints(line)
|
x1, y1, x2, y2 = ints(line)
|
||||||
for x in range(x1, x2 + 1):
|
for x in range(x1, x2 + 1):
|
||||||
for y in range(y1, y2 + 1):
|
for y in range(y1, y2 + 1):
|
||||||
if part_2:
|
if "off" in line:
|
||||||
if "on" in line:
|
if (x, y) in ons:
|
||||||
lights[x][y] += 1
|
ons.remove((x, y))
|
||||||
elif "off" in line:
|
ons2[(x, y)] = max(ons2[(x, y)] - 1, 0)
|
||||||
lights[x][y] = max(0, lights[x][y] - 1)
|
elif "on" in line:
|
||||||
|
ons.add((x, y))
|
||||||
|
ons2[(x, y)] += 1
|
||||||
|
elif "toggle" in line:
|
||||||
|
if (x, y) in ons:
|
||||||
|
ons.remove((x, y))
|
||||||
else:
|
else:
|
||||||
lights[x][y] += 2
|
ons.add((x, y))
|
||||||
|
ons2[(x, y)] += 2
|
||||||
else:
|
else:
|
||||||
if "on" in line:
|
assert False
|
||||||
lights[x][y] = 1
|
|
||||||
elif "off" in line:
|
|
||||||
lights[x][y] = 0
|
|
||||||
else:
|
|
||||||
if lights[x][y] == 1:
|
|
||||||
lights[x][y] = 0
|
|
||||||
else:
|
|
||||||
lights[x][y] = 1
|
|
||||||
|
|
||||||
res = 0
|
print(len(ons))
|
||||||
for row in lights:
|
print(sum(ons2.values()))
|
||||||
res += sum(row)
|
|
||||||
print(res)
|
|
||||||
|
|||||||
126
2015/d7.py
126
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:
|
def run(wires={}):
|
||||||
|
def get(a):
|
||||||
|
try:
|
||||||
|
return int(a)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
if a in wires:
|
||||||
|
return wires[a]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
while "a" not in wires:
|
||||||
for line in data.splitlines():
|
for line in data.splitlines():
|
||||||
lhs, rhs = line.split(" -> ")
|
lhs, rhs = line.split(" -> ")
|
||||||
if part_2 and rhs == "b":
|
if rhs in wires:
|
||||||
continue
|
continue
|
||||||
lhs = lhs.strip()
|
match lhs.split():
|
||||||
if "NOT" in lhs:
|
case [a, "AND", b]:
|
||||||
op, op1 = lhs.split(" ")
|
a, b = get(a), get(b)
|
||||||
assert op == "NOT"
|
if a is not None and b is not None:
|
||||||
try:
|
wires[rhs] = a & b
|
||||||
op1 = int(op1)
|
case [a, "OR", b]:
|
||||||
gates[rhs] = ~op1 & 0xffff
|
a, b = get(a), get(b)
|
||||||
except ValueError:
|
if a is not None and b is not None:
|
||||||
if op1 in gates and isinstance(gates[op1], int):
|
wires[rhs] = a | b
|
||||||
gates[rhs] = ~gates[op1] & 0xffff
|
case [a, "LSHIFT", b]:
|
||||||
elif "OR" in lhs:
|
a, b = get(a), get(b)
|
||||||
op1, op, op2 = lhs.split(" ")
|
if a is not None and b is not None:
|
||||||
assert op == "OR"
|
wires[rhs] = a << b
|
||||||
try:
|
case [a, "RSHIFT", b]:
|
||||||
op1 = int(op1)
|
a, b = get(a), get(b)
|
||||||
except ValueError:
|
if a is not None and b is not None:
|
||||||
if op1 in gates and isinstance(gates[op1], int):
|
wires[rhs] = a >> b
|
||||||
op1 = gates[op1]
|
case ["NOT", a]:
|
||||||
try:
|
a = get(a)
|
||||||
op2 = int(op2)
|
if a is not None:
|
||||||
except ValueError:
|
wires[rhs] = ~a & 0xFFFF
|
||||||
if op2 in gates and isinstance(gates[op2], int):
|
case [a]:
|
||||||
op2 = gates[op2]
|
a = get(a)
|
||||||
|
if a is not None:
|
||||||
|
wires[rhs] = a
|
||||||
|
return wires
|
||||||
|
|
||||||
if not (isinstance(op1, int) and isinstance(op2, int)):
|
|
||||||
continue
|
|
||||||
gates[rhs] = (op1 | op2)
|
|
||||||
elif "AND" in lhs:
|
|
||||||
op1, op, op2 = lhs.split(" ")
|
|
||||||
assert op == "AND"
|
|
||||||
try:
|
|
||||||
op1 = int(op1)
|
|
||||||
except ValueError:
|
|
||||||
if op1 in gates and isinstance(gates[op1], int):
|
|
||||||
op1 = gates[op1]
|
|
||||||
try:
|
|
||||||
op2 = int(op2)
|
|
||||||
except ValueError:
|
|
||||||
if op2 in gates and isinstance(gates[op2], int):
|
|
||||||
op2 = gates[op2]
|
|
||||||
|
|
||||||
if not (isinstance(op1, int) and isinstance(op2, int)):
|
a = run()["a"]
|
||||||
continue
|
print(a)
|
||||||
gates[rhs] = (op1 & op2)
|
|
||||||
elif "LSHIFT" in lhs:
|
|
||||||
op1, op, op2 = lhs.split(" ")
|
|
||||||
op2 = int(op2)
|
|
||||||
try:
|
|
||||||
op1 = int(op1)
|
|
||||||
except ValueError:
|
|
||||||
if op1 in gates:
|
|
||||||
op1 = gates[op1]
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
gates[rhs] = (op1 << op2) & 0xffff
|
|
||||||
elif "RSHIFT" in lhs:
|
|
||||||
op1, op, op2 = lhs.split(" ")
|
|
||||||
op2 = int(op2)
|
|
||||||
try:
|
|
||||||
op1 = int(op1)
|
|
||||||
except ValueError:
|
|
||||||
if op1 in gates:
|
|
||||||
op1 = gates[op1]
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
gates[rhs] = (op1 >> op2) & 0xffff
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
lhs = int(lhs)
|
|
||||||
gates[rhs] = lhs
|
|
||||||
except ValueError:
|
|
||||||
if lhs in gates:
|
|
||||||
gates[rhs] = gates[lhs]
|
|
||||||
|
|
||||||
print(gates["a"])
|
a2 = run(wires={"b": a})["a"]
|
||||||
|
print(a2)
|
||||||
|
|||||||
29
2015/d8.py
29
2015/d8.py
@@ -1,26 +1,25 @@
|
|||||||
from lib import *
|
from lib import get_data
|
||||||
|
import re
|
||||||
|
|
||||||
data = open(0).read()
|
data = get_data(__file__)
|
||||||
part_1 = False
|
|
||||||
|
|
||||||
if part_1:
|
r1 = re.compile(r"\\x[0-9a-f][0-9a-f]")
|
||||||
r1 = re.compile(r"\\x[0-9a-f][0-9a-f]")
|
r2 = re.compile(r"\\\\")
|
||||||
r2 = re.compile(r"\\\\")
|
r3 = re.compile(r"\\\"")
|
||||||
r3 = re.compile(r"\\\"")
|
|
||||||
|
|
||||||
enc, mem = 0, 0
|
enc, mem = 0, 0
|
||||||
for line in data.splitlines():
|
for line in data.splitlines():
|
||||||
mem += len(line)
|
mem += len(line)
|
||||||
line = r1.sub("^", line)
|
line = r1.sub("^", line)
|
||||||
line = r2.sub("^", line)
|
line = r2.sub("^", line)
|
||||||
line = r3.sub("^", line)
|
line = r3.sub("^", line)
|
||||||
enc += len(line) - 2
|
enc += len(line) - 2
|
||||||
print(mem - enc)
|
print(mem - enc)
|
||||||
else:
|
|
||||||
ori, enc = 0, 0
|
ori, enc = 0, 0
|
||||||
for line in data.splitlines():
|
for line in data.splitlines():
|
||||||
ori += len(line)
|
ori += len(line)
|
||||||
line = line.replace("\\", "\\\\")
|
line = line.replace("\\", "\\\\")
|
||||||
line = line.replace("\"", "\\\"")
|
line = line.replace('"', '\\"')
|
||||||
enc += len(line) + 2
|
enc += len(line) + 2
|
||||||
print(enc - ori)
|
print(enc - ori)
|
||||||
|
|||||||
49
2015/d9.py
49
2015/d9.py
@@ -1,42 +1,27 @@
|
|||||||
from lib import *
|
from lib import get_data
|
||||||
from typing import Iterator
|
from itertools import permutations
|
||||||
|
|
||||||
# Just for fun instead of using itertools.permutations.
|
data = get_data(__file__)
|
||||||
def permutations(xs) -> Iterator:
|
|
||||||
assert len(xs) > 0
|
|
||||||
if len(xs) == 1:
|
|
||||||
yield xs
|
|
||||||
else:
|
|
||||||
x = xs.pop()
|
|
||||||
for p in permutations(xs):
|
|
||||||
for i in range(len(p) + 1):
|
|
||||||
pn = list(p)
|
|
||||||
pn.insert(i, x)
|
|
||||||
yield pn
|
|
||||||
|
|
||||||
part_1 = False
|
|
||||||
data = open(0).read()
|
|
||||||
|
|
||||||
dists = {}
|
dists = {}
|
||||||
nodes = set()
|
nodes = set()
|
||||||
for line in data.splitlines():
|
for line in data.splitlines():
|
||||||
path, dist = line.split(" = ")
|
a, _, b, _, d = line.split()
|
||||||
dist = int(dist)
|
|
||||||
a, b = path.split(" to ")
|
|
||||||
nodes.add(a)
|
nodes.add(a)
|
||||||
nodes.add(b)
|
nodes.add(b)
|
||||||
|
dist = int(d)
|
||||||
dists[(a, b)] = dist
|
dists[(a, b)] = dist
|
||||||
dists[(b, a)] = dist
|
dists[(b, a)] = dist
|
||||||
|
|
||||||
if part_1:
|
d_min = 10**12
|
||||||
mdist = 10**12
|
d_max = 0
|
||||||
for route in permutations(nodes):
|
for p in permutations(list(nodes)):
|
||||||
dist = sum([dists[(route[i], route[i + 1])] for i in range(len(route) - 1)])
|
d = 0
|
||||||
mdist = min(dist, mdist)
|
for i in range(len(p) - 1):
|
||||||
print(mdist)
|
a, b = p[i], p[i + 1]
|
||||||
else:
|
d += dists[(a, b)]
|
||||||
mdist = 0
|
d_min = min(d, d_min)
|
||||||
for route in permutations(nodes):
|
d_max = max(d, d_max)
|
||||||
dist = sum([dists[(route[i], route[i + 1])] for i in range(len(route) - 1)])
|
|
||||||
mdist = max(dist, mdist)
|
print(d_min)
|
||||||
print(mdist)
|
print(d_max)
|
||||||
|
|||||||
109
2018/d19.py
109
2018/d19.py
@@ -2,40 +2,133 @@ from lib import get_data, str_to_ints
|
|||||||
import d16
|
import d16
|
||||||
|
|
||||||
|
|
||||||
def part_1(data):
|
def print_regs(regs):
|
||||||
|
print(" ".join([f"regs[{i}]={regs[i]:10}" for i in range(len(regs))]) + " (ip)")
|
||||||
|
|
||||||
|
|
||||||
|
def print_inst(i, insts, ip):
|
||||||
|
op, arg1, arg2, dest = insts[i]
|
||||||
|
dest = f"regs[{dest}]" if dest != ip else "ip"
|
||||||
|
match op:
|
||||||
|
case "addi":
|
||||||
|
print(f"{i:2} {dest} = regs[{arg1}] + {arg2}")
|
||||||
|
case "addr":
|
||||||
|
print(f"{i:2} {dest} = regs[{arg1}] + regs[{arg2}]")
|
||||||
|
case "muli":
|
||||||
|
print(f"{i:2} {dest} = regs[{arg1}] * {arg2}")
|
||||||
|
case "mulr":
|
||||||
|
print(f"{i:2} {dest} = regs[{arg1}] * regs[{arg2}]")
|
||||||
|
case "seti":
|
||||||
|
print(f"{i:2} {dest} = {arg1}")
|
||||||
|
case "setr":
|
||||||
|
print(f"{i:2} {dest} = regs[{arg1}]")
|
||||||
|
case "eqrr":
|
||||||
|
print(f"{i:2} {dest} = (regs[{arg1}] == regs[{arg2}]) ? 1 : 0")
|
||||||
|
case "gtrr":
|
||||||
|
print(f"{i:2} {dest} = (regs[{arg1}] > regs[{arg2}]) ? 1 : 0")
|
||||||
|
case _:
|
||||||
|
print(f"{i:2} {dest} = {op}({arg1}, {arg2})")
|
||||||
|
|
||||||
|
|
||||||
|
def sum_of_divisors(n):
|
||||||
|
total = 1
|
||||||
|
for i in range(2, int(n**0.5) + 1):
|
||||||
|
if n % i == 0:
|
||||||
|
total += i
|
||||||
|
if i != n // i: # do not count square root twice
|
||||||
|
total += n // i
|
||||||
|
if n != 1:
|
||||||
|
total += n
|
||||||
|
return total
|
||||||
|
|
||||||
|
|
||||||
|
def run(data, break_after=None, reg_zero_init=0):
|
||||||
ip = None
|
ip = None
|
||||||
regs = [0 for _ in range(6)]
|
regs = [0 for _ in range(6)]
|
||||||
|
regs[0] = reg_zero_init
|
||||||
regs[0] = 1 # part 2
|
|
||||||
|
|
||||||
insts = []
|
insts = []
|
||||||
for line in data.splitlines():
|
for line in data.splitlines():
|
||||||
if line.startswith("#"):
|
if line.startswith("#"):
|
||||||
ip, = str_to_ints(line)
|
(ip,) = str_to_ints(line)
|
||||||
else:
|
else:
|
||||||
fs = line.split()
|
fs = line.split()
|
||||||
vals = str_to_ints(line)
|
vals = str_to_ints(line)
|
||||||
insts.append([fs[0]] + vals)
|
insts.append([fs[0]] + vals)
|
||||||
|
|
||||||
|
count = 0
|
||||||
assert ip is not None
|
assert ip is not None
|
||||||
while regs[ip] < len(insts):
|
while regs[ip] < len(insts):
|
||||||
|
if break_after is not None and count > break_after:
|
||||||
|
break
|
||||||
inst = insts[regs[ip]]
|
inst = insts[regs[ip]]
|
||||||
f = getattr(d16, inst[0])
|
f = getattr(d16, inst[0])
|
||||||
# if inst[1:] == [3, 1, 3]:
|
|
||||||
# regs[3] = regs[1]
|
|
||||||
# regs[4] = regs[1]
|
|
||||||
f(regs, *inst[1:])
|
f(regs, *inst[1:])
|
||||||
regs[ip] += 1
|
regs[ip] += 1
|
||||||
print(regs, regs[-1] + 1)
|
count += 1
|
||||||
|
return regs
|
||||||
|
|
||||||
|
|
||||||
|
def part_1(data):
|
||||||
|
regs = run(data)
|
||||||
print(regs[0])
|
print(regs[0])
|
||||||
|
|
||||||
|
|
||||||
|
def part_2(data):
|
||||||
|
regs = run(data, 10_000, 1)
|
||||||
|
# by analysing the code we can see that the int computer counts the sum of
|
||||||
|
# the number of divisors
|
||||||
|
print(sum_of_divisors(regs[1]))
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
data = get_data(__file__)
|
data = get_data(__file__)
|
||||||
part_1(data)
|
part_1(data)
|
||||||
|
part_2(data)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
||||||
|
|
||||||
|
listing = """
|
||||||
|
ip=5
|
||||||
|
0 ip = regs[5] + 16
|
||||||
|
1 regs[2] = 1
|
||||||
|
2 regs[4] = 1
|
||||||
|
3 regs[3] = regs[2] * regs[4] |
|
||||||
|
4 regs[3] = (regs[3] == regs[1]) ? 1 : 0 |
|
||||||
|
5 ip = regs[3] + regs[5] | jump to 7 if regs[3] == regs[1]
|
||||||
|
6 ip = regs[5] + 1 | skip 7
|
||||||
|
7 regs[0] = regs[2] + regs[0]
|
||||||
|
8 regs[4] = regs[4] + 1 | regs[4] += 1
|
||||||
|
9 regs[3] = (regs[4] > regs[1]) ? 1 : 0 |
|
||||||
|
10 ip = regs[5] + regs[3] | jump to 12 if regs[4] > regs[1]
|
||||||
|
11 ip = 2 | goto 3
|
||||||
|
12 regs[2] = regs[2] + 1 | regs[2] += 1
|
||||||
|
13 regs[3] = (regs[2] > regs[1]) ? 1 : 0 |
|
||||||
|
14 ip = regs[3] + regs[5] | jump to 16 if regs[2] > regs[1]
|
||||||
|
15 ip = 1 | goto 2
|
||||||
|
16 ip = regs[5] * regs[5]
|
||||||
|
17 regs[1] = regs[1] + 2
|
||||||
|
18 regs[1] = regs[1] * regs[1]
|
||||||
|
19 regs[1] = regs[5] * regs[1]
|
||||||
|
20 regs[1] = regs[1] * 11
|
||||||
|
21 regs[3] = regs[3] + 6
|
||||||
|
22 regs[3] = regs[3] * regs[5]
|
||||||
|
23 regs[3] = regs[3] + 15
|
||||||
|
24 regs[1] = regs[1] + regs[3]
|
||||||
|
25 ip = regs[5] + regs[0]
|
||||||
|
26 ip = 0
|
||||||
|
27 regs[3] = regs[5]
|
||||||
|
28 regs[3] = regs[3] * regs[5]
|
||||||
|
29 regs[3] = regs[5] + regs[3]
|
||||||
|
30 regs[3] = regs[5] * regs[3]
|
||||||
|
31 regs[3] = regs[3] * 14
|
||||||
|
32 regs[3] = regs[3] * regs[5]
|
||||||
|
33 regs[1] = regs[1] + regs[3]
|
||||||
|
34 regs[0] = 0
|
||||||
|
35 ip = 0
|
||||||
|
|
||||||
|
hypothesis: usm of numbers that divide the number in regs[0]
|
||||||
|
"""
|
||||||
|
|||||||
94
2018/d20.py
Normal file
94
2018/d20.py
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
from lib import get_data, add2
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
DIRS = {
|
||||||
|
"N": (-1, 0),
|
||||||
|
"E": (0, 1),
|
||||||
|
"S": (1, 0),
|
||||||
|
"W": (0, -1),
|
||||||
|
}
|
||||||
|
|
||||||
|
data = get_data(__file__).strip()
|
||||||
|
g = defaultdict(set)
|
||||||
|
seen = set()
|
||||||
|
stack: list[tuple[tuple, int, list]] = [((0, 0), 0, [])]
|
||||||
|
while len(stack) > 0:
|
||||||
|
pos, i, i_outs = stack.pop()
|
||||||
|
|
||||||
|
c = (pos, i, tuple(i_outs))
|
||||||
|
if c in seen:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
seen.add(c)
|
||||||
|
|
||||||
|
assert i is not None
|
||||||
|
while i < len(data):
|
||||||
|
c = data[i]
|
||||||
|
if c in DIRS.keys():
|
||||||
|
npos = add2(pos, DIRS[c])
|
||||||
|
g[pos].add(npos)
|
||||||
|
g[npos].add(pos)
|
||||||
|
pos = npos
|
||||||
|
i += 1
|
||||||
|
elif c == "(":
|
||||||
|
to_continue = [i + 1]
|
||||||
|
open_count = 0
|
||||||
|
j_out = None
|
||||||
|
for j in range(i + 1, len(data)):
|
||||||
|
c = data[j]
|
||||||
|
if c == "|" and open_count == 0:
|
||||||
|
to_continue.append(j + 1)
|
||||||
|
elif c == "(":
|
||||||
|
open_count += 1
|
||||||
|
elif c == ")" and open_count != 0:
|
||||||
|
open_count -= 1
|
||||||
|
elif c == ")" and open_count == 0:
|
||||||
|
j_out = j
|
||||||
|
break
|
||||||
|
assert j_out is not None
|
||||||
|
|
||||||
|
for new_i in to_continue:
|
||||||
|
new_i_outs = list(i_outs)
|
||||||
|
new_i_outs.append(j_out)
|
||||||
|
stack.append((pos, new_i, new_i_outs))
|
||||||
|
break
|
||||||
|
elif c == "$":
|
||||||
|
break
|
||||||
|
elif c == ")" and len(i_outs) == 0:
|
||||||
|
assert False, "Encountered | without i_out"
|
||||||
|
elif c == ")":
|
||||||
|
i_new = i_outs.pop()
|
||||||
|
assert i == i_new
|
||||||
|
i += 1
|
||||||
|
elif c == "^":
|
||||||
|
i += 1
|
||||||
|
elif c == "|" and len(i_outs) == 0:
|
||||||
|
assert False, "Encountered | without i_out"
|
||||||
|
elif c == "|":
|
||||||
|
i = i_outs.pop()
|
||||||
|
i += 1
|
||||||
|
else:
|
||||||
|
assert False
|
||||||
|
|
||||||
|
seen = set()
|
||||||
|
dists = {}
|
||||||
|
xs = [(0, 0)]
|
||||||
|
steps = 0
|
||||||
|
over_thousand = set()
|
||||||
|
while len(xs) > 0:
|
||||||
|
nxs = []
|
||||||
|
for x in xs:
|
||||||
|
if x in seen:
|
||||||
|
continue
|
||||||
|
if steps >= 1000:
|
||||||
|
over_thousand.add(x)
|
||||||
|
seen.add(x)
|
||||||
|
dists[x] = steps
|
||||||
|
for nb in g[x]:
|
||||||
|
if not nb in seen:
|
||||||
|
nxs.append(nb)
|
||||||
|
xs = nxs
|
||||||
|
steps += 1
|
||||||
|
|
||||||
|
print(max(dists.values()))
|
||||||
|
print(len(over_thousand))
|
||||||
52
2018/d21.py
Normal file
52
2018/d21.py
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
from lib import get_data, str_to_ints
|
||||||
|
import d16
|
||||||
|
|
||||||
|
|
||||||
|
def run(data, break_after=None, reg_zero_init=0):
|
||||||
|
ip = None
|
||||||
|
regs = [0 for _ in range(6)]
|
||||||
|
regs[0] = reg_zero_init
|
||||||
|
|
||||||
|
insts = []
|
||||||
|
for line in data.splitlines():
|
||||||
|
if line.startswith("#"):
|
||||||
|
(ip,) = str_to_ints(line)
|
||||||
|
else:
|
||||||
|
fs = line.split()
|
||||||
|
vals = str_to_ints(line)
|
||||||
|
insts.append([fs[0]] + vals)
|
||||||
|
|
||||||
|
count = 0
|
||||||
|
assert ip is not None
|
||||||
|
seen = set()
|
||||||
|
last_added = None
|
||||||
|
while regs[ip] < len(insts):
|
||||||
|
if break_after is not None and count > break_after:
|
||||||
|
break
|
||||||
|
if regs[ip] == 28:
|
||||||
|
r4 = regs[4]
|
||||||
|
|
||||||
|
if len(seen) == 0:
|
||||||
|
print(r4)
|
||||||
|
|
||||||
|
if r4 in seen:
|
||||||
|
print(last_added)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
seen.add(r4)
|
||||||
|
last_added = r4
|
||||||
|
|
||||||
|
inst = insts[regs[ip]]
|
||||||
|
f = getattr(d16, inst[0])
|
||||||
|
f(regs, *inst[1:])
|
||||||
|
regs[ip] += 1
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
data = get_data(__file__)
|
||||||
|
run(data, reg_zero_init=333)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
89
2018/d22.py
Normal file
89
2018/d22.py
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
from lib import get_data, str_to_ints, A_Star
|
||||||
|
|
||||||
|
data = get_data(__file__)
|
||||||
|
|
||||||
|
depth, x_target, y_target = str_to_ints(data)
|
||||||
|
# depth, x_target, y_target = 510, 10, 10
|
||||||
|
# depth, x_target, y_target = 9171, 7, 721
|
||||||
|
# print(depth, x_target, y_target)
|
||||||
|
|
||||||
|
x_area_max = x_target * 10
|
||||||
|
y_area_max = y_target * 10
|
||||||
|
|
||||||
|
maze = [[None for _ in range(x_area_max + 1)] for _ in range(y_area_max + 1)]
|
||||||
|
|
||||||
|
maze[0][0] = 0
|
||||||
|
maze[y_target][x_target] = 0
|
||||||
|
|
||||||
|
mod = 20183
|
||||||
|
for x in range(x_area_max + 1):
|
||||||
|
maze[0][x] = x * 16807 % mod
|
||||||
|
|
||||||
|
for y in range(y_area_max + 1):
|
||||||
|
maze[y][0] = y * 48271 % mod
|
||||||
|
|
||||||
|
for y in range(1, y_area_max + 1):
|
||||||
|
for x in range(1, x_area_max + 1):
|
||||||
|
if x == x_target and y == y_target:
|
||||||
|
continue
|
||||||
|
assert maze[y][x] is None
|
||||||
|
geo_index = ((maze[y][x - 1] + depth) * (maze[y - 1][x] + depth)) % mod
|
||||||
|
maze[y][x] = geo_index
|
||||||
|
|
||||||
|
t = 0
|
||||||
|
for y in range(y_target + 1):
|
||||||
|
for x in range(x_target + 1):
|
||||||
|
t += ((maze[y][x] + depth) % mod) % 3
|
||||||
|
print(t)
|
||||||
|
|
||||||
|
for y in range(y_area_max + 1):
|
||||||
|
for x in range(x_area_max + 1):
|
||||||
|
maze[y][x] = ((maze[y][x] + depth) % mod) % 3
|
||||||
|
|
||||||
|
|
||||||
|
def allowed(area, tool):
|
||||||
|
# 0 = rocky, 1 = wet, 2 = narrow
|
||||||
|
# 0 = torch, 1 = climbing, 2 = neither
|
||||||
|
return (
|
||||||
|
(area == 0 and (tool in [0, 1]))
|
||||||
|
or (area == 1 and (tool in [1, 2]))
|
||||||
|
or (area == 2 and (tool in [0, 2]))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def neighbors(state):
|
||||||
|
x, y, tool = state
|
||||||
|
r = []
|
||||||
|
area = maze[y][x]
|
||||||
|
for t in range(3):
|
||||||
|
if allowed(area, t) and t != tool:
|
||||||
|
r.append((x, y, t))
|
||||||
|
|
||||||
|
for dx, dy in [(0, 1), (0, -1), (1, 0), (-1, 0)]:
|
||||||
|
nx, ny = x + dx, y + dy
|
||||||
|
if not (nx >= 0 and nx < x_area_max and ny >= 0 and ny < y_area_max):
|
||||||
|
continue
|
||||||
|
if not allowed(maze[ny][nx], tool):
|
||||||
|
continue
|
||||||
|
r.append((nx, ny, tool))
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
def distance(a, b):
|
||||||
|
if a == 0:
|
||||||
|
return 0
|
||||||
|
elif a[2] != b[2]:
|
||||||
|
assert a[0] == b[0] and a[1] == b[1]
|
||||||
|
return 7
|
||||||
|
else:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
a = A_Star(
|
||||||
|
starts=[(0, 0, 0)],
|
||||||
|
is_goal=lambda s: s[0] == x_target and s[1] == y_target and s[2] == 0,
|
||||||
|
h=lambda s: abs(s[0] - x_target) + abs(s[1] - y_target),
|
||||||
|
d=distance,
|
||||||
|
neighbors=neighbors,
|
||||||
|
)
|
||||||
|
print(a.cost)
|
||||||
45
2018/d23.py
Normal file
45
2018/d23.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
from lib import get_data, ints
|
||||||
|
|
||||||
|
data = get_data(__file__)
|
||||||
|
|
||||||
|
X, Y, Z, RADIUS = 0, 1, 2, 3
|
||||||
|
|
||||||
|
|
||||||
|
def dist(a, b):
|
||||||
|
return sum(abs(x - y) for x, y in zip(a[:RADIUS], b[:RADIUS]))
|
||||||
|
|
||||||
|
|
||||||
|
bots = [ints(line) for line in data.splitlines()]
|
||||||
|
max_bot = max(bots, key=lambda b: b[RADIUS])
|
||||||
|
r = max_bot[3]
|
||||||
|
t = sum(1 for bot in bots if dist(bot, max_bot) <= r)
|
||||||
|
print(t)
|
||||||
|
|
||||||
|
from z3 import Int, If, Abs, Sum, IntVal, Optimize, set_param
|
||||||
|
|
||||||
|
|
||||||
|
x = Int("x")
|
||||||
|
y = Int("y")
|
||||||
|
z = Int("z")
|
||||||
|
|
||||||
|
z3zero = IntVal(0)
|
||||||
|
z3one = IntVal(1)
|
||||||
|
n_out_of_range = Int("n_out_of_range")
|
||||||
|
dist_origin = Int("dist_origin")
|
||||||
|
|
||||||
|
bots_out_of_range = [
|
||||||
|
If(Abs(b[X] - x) + Abs(b[Y] - y) + Abs(b[Z] - z) > b[RADIUS], z3one, z3zero)
|
||||||
|
for b in bots
|
||||||
|
]
|
||||||
|
|
||||||
|
set_param("parallel.enable", True)
|
||||||
|
|
||||||
|
opt = Optimize()
|
||||||
|
opt.add(n_out_of_range == Sum(bots_out_of_range))
|
||||||
|
opt.add(dist_origin == Abs(x) + Abs(y) + Abs(z))
|
||||||
|
opt.minimize(n_out_of_range)
|
||||||
|
opt.minimize(dist_origin)
|
||||||
|
|
||||||
|
res = opt.check()
|
||||||
|
m = opt.model()
|
||||||
|
print(m[dist_origin])
|
||||||
179
2018/d24.py
Normal file
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))
|
||||||
36
2019/d12.py
36
2019/d12.py
@@ -1,4 +1,5 @@
|
|||||||
from lib import get_data, str_to_ints
|
from lib import get_data, str_to_ints
|
||||||
|
from math import lcm
|
||||||
|
|
||||||
|
|
||||||
def freeze(coords, velos):
|
def freeze(coords, velos):
|
||||||
@@ -40,7 +41,40 @@ def part_1(data):
|
|||||||
|
|
||||||
|
|
||||||
def part_2(data):
|
def part_2(data):
|
||||||
raise Exception("TBD")
|
steps = []
|
||||||
|
for i in range(3):
|
||||||
|
coords = []
|
||||||
|
velos = []
|
||||||
|
|
||||||
|
for line in data.splitlines():
|
||||||
|
coords.append(str_to_ints(line)[i])
|
||||||
|
velos.append(0)
|
||||||
|
|
||||||
|
seen = {}
|
||||||
|
for step in range(1_000_000):
|
||||||
|
state = tuple(coords) + tuple(velos)
|
||||||
|
if state in seen:
|
||||||
|
steps.append(step)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
seen[state] = step
|
||||||
|
|
||||||
|
for i in range(len(coords)):
|
||||||
|
for j in range(i + 1, len(coords)):
|
||||||
|
if coords[i] > coords[j]:
|
||||||
|
velos[i] -= 1
|
||||||
|
velos[j] += 1
|
||||||
|
elif coords[i] < coords[j]:
|
||||||
|
velos[i] += 1
|
||||||
|
velos[j] -= 1
|
||||||
|
|
||||||
|
# update coords
|
||||||
|
for i in range(len(coords)):
|
||||||
|
coords[i] += velos[i]
|
||||||
|
|
||||||
|
# Intuition: Find when position repeats on each axis and then find lowest
|
||||||
|
# common multiple for all three values.
|
||||||
|
print(lcm(*steps))
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|||||||
37
2019/d16.py
37
2019/d16.py
@@ -40,7 +40,7 @@ def phase_with_offset(digits_in, pattern, offset):
|
|||||||
digits_out = digits_in.copy()
|
digits_out = digits_in.copy()
|
||||||
for round in range(offset, len(digits_in)):
|
for round in range(offset, len(digits_in)):
|
||||||
i, out = 0, 0
|
i, out = 0, 0
|
||||||
print(round)
|
# print(round)
|
||||||
|
|
||||||
pattern_value = pattern[((i + 1) // (round + 1)) % len(pattern)]
|
pattern_value = pattern[((i + 1) // (round + 1)) % len(pattern)]
|
||||||
if pattern_value == 0:
|
if pattern_value == 0:
|
||||||
@@ -53,8 +53,6 @@ def phase_with_offset(digits_in, pattern, offset):
|
|||||||
out += pattern_value * sum(
|
out += pattern_value * sum(
|
||||||
digits_in[i : min(i + 1 + round, len(digits_in))]
|
digits_in[i : min(i + 1 + round, len(digits_in))]
|
||||||
)
|
)
|
||||||
# for j in range(i, min(i + 1 + round, len(digits_in))):
|
|
||||||
# out += (pattern_value * digits_in[j])
|
|
||||||
i += round + 1
|
i += round + 1
|
||||||
|
|
||||||
out = abs(out) % 10
|
out = abs(out) % 10
|
||||||
@@ -71,16 +69,33 @@ def part_1(data):
|
|||||||
input = phase_with_offset(input, pattern, 0)
|
input = phase_with_offset(input, pattern, 0)
|
||||||
print("".join(map(str, input[:8])))
|
print("".join(map(str, input[:8])))
|
||||||
|
|
||||||
input = list(map(int, (data.strip()))) * 10_000
|
out = list(map(int, (data.strip()))) * 10_000
|
||||||
offset = int("".join(map(str, input[:7]))) - 200
|
offset = int("".join(map(str, out[:7])))
|
||||||
|
for _ in range(100):
|
||||||
|
for i in range(len(out) - 2, len(out) - 1_000_000, -1):
|
||||||
|
out[i] = abs(out[i] + out[i + 1]) % 10
|
||||||
|
print("".join(map(str, out[offset : offset + 8])))
|
||||||
|
|
||||||
for i in range(100):
|
# digits = 40
|
||||||
print(i)
|
# for round in range(digits):
|
||||||
input = phase_with_offset(input, pattern, offset)
|
# s = ""
|
||||||
print(input)
|
# for i in range(digits):
|
||||||
|
# pattern_i = ((i + 1) // (round + 1)) % len(pattern)
|
||||||
return
|
# pattern_value = pattern[pattern_i]
|
||||||
|
# if pattern_value == 0:
|
||||||
|
# s += " "
|
||||||
|
# elif pattern_value == 1:
|
||||||
|
# s += "+"
|
||||||
|
# elif pattern_value == -1:
|
||||||
|
# s += "-"
|
||||||
|
# else:
|
||||||
|
# assert False
|
||||||
|
# print(s)
|
||||||
|
# return
|
||||||
|
|
||||||
|
# Just here to document my thought process. Mental hack: Assume that you
|
||||||
|
# have the capability to solve the problem easily.
|
||||||
|
#
|
||||||
# What do I know?
|
# What do I know?
|
||||||
#
|
#
|
||||||
# 1. There is a solution. Other people have solved it.
|
# 1. There is a solution. Other people have solved it.
|
||||||
|
|||||||
56
2019/d17.py
56
2019/d17.py
@@ -51,9 +51,11 @@ def find_path(g):
|
|||||||
path += "R"
|
path += "R"
|
||||||
dir = DIRS[(DIRS.index(dir) + 1) % len(DIRS)]
|
dir = DIRS[(DIRS.index(dir) + 1) % len(DIRS)]
|
||||||
g[pos] = DIRCHAR[DIRS.index(dir)]
|
g[pos] = DIRCHAR[DIRS.index(dir)]
|
||||||
g.print()
|
|
||||||
input()
|
# For debugging:
|
||||||
print()
|
# g.print()
|
||||||
|
# input()
|
||||||
|
# print()
|
||||||
path = path.replace("RRR", "L")
|
path = path.replace("RRR", "L")
|
||||||
return path
|
return path
|
||||||
|
|
||||||
@@ -81,14 +83,48 @@ def part_1(data):
|
|||||||
# g[(r, c)] = 'o'
|
# g[(r, c)] = 'o'
|
||||||
result += r * c
|
result += r * c
|
||||||
print(result)
|
print(result)
|
||||||
g.print()
|
# g.print()
|
||||||
path = find_path(g)
|
|
||||||
print(path)
|
|
||||||
|
|
||||||
# xs = str_to_ints(data)
|
# Merge F commands into counts
|
||||||
# xs[0] = 2
|
path = find_path(g)
|
||||||
# a = Amp(xs)
|
path = list(path)
|
||||||
# a.go()
|
new_path = []
|
||||||
|
i = 0
|
||||||
|
while i < len(path):
|
||||||
|
if path[i] == "R":
|
||||||
|
new_path.append("R")
|
||||||
|
i += 1
|
||||||
|
elif path[i] == "L":
|
||||||
|
new_path.append("L")
|
||||||
|
i += 1
|
||||||
|
elif path[i] == "F":
|
||||||
|
count = 0
|
||||||
|
while i < len(path) and path[i] == "F":
|
||||||
|
count += 1
|
||||||
|
i += 1
|
||||||
|
new_path.append(str(count))
|
||||||
|
else:
|
||||||
|
assert False
|
||||||
|
path = new_path
|
||||||
|
print("Manually translate into commands:", "".join(path))
|
||||||
|
|
||||||
|
# manually created from above output
|
||||||
|
inst = (
|
||||||
|
"A,A,B,C,B,C,B,C,A,C\n"
|
||||||
|
"R,6,L,8,R,8\n"
|
||||||
|
"R,4,R,6,R,6,R,4,R,4\n"
|
||||||
|
"L,8,R,6,L,10,L,10\n"
|
||||||
|
"n\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
xs = str_to_ints(data)
|
||||||
|
xs[0] = 2
|
||||||
|
a = Amp(xs)
|
||||||
|
for c in inst:
|
||||||
|
a.feed(ord(c))
|
||||||
|
while not a.done:
|
||||||
|
a.go()
|
||||||
|
print(a.outputs[-1])
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|||||||
199
2019/d18.py
Normal file
199
2019/d18.py
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
from lib import get_data, Grid2D, LETTERS_UPPER, LETTERS_LOWER, add2
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
LETTERS = LETTERS_UPPER + LETTERS_LOWER
|
||||||
|
|
||||||
|
data = """#############
|
||||||
|
#g#f.D#..h#l#
|
||||||
|
#F###e#E###.#
|
||||||
|
#dCba...BcIJ#
|
||||||
|
#####.@.#####
|
||||||
|
#nK.L...G...#
|
||||||
|
#M###N#H###.#
|
||||||
|
#o#m..#i#jk.#
|
||||||
|
#############"""
|
||||||
|
|
||||||
|
|
||||||
|
def part_1(data):
|
||||||
|
g = Grid2D(data)
|
||||||
|
(start,) = g.find("@")
|
||||||
|
starts = [start]
|
||||||
|
|
||||||
|
graph = defaultdict(set)
|
||||||
|
starts_seen = set()
|
||||||
|
|
||||||
|
while starts:
|
||||||
|
start = starts.pop()
|
||||||
|
if start in starts_seen:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
starts_seen.add(start)
|
||||||
|
|
||||||
|
start_symbol = g[start]
|
||||||
|
xs = [start]
|
||||||
|
seen = set()
|
||||||
|
|
||||||
|
for steps in range(1_000):
|
||||||
|
nxs = []
|
||||||
|
|
||||||
|
for x in xs:
|
||||||
|
if x in seen:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
seen.add(x)
|
||||||
|
|
||||||
|
for nb in g.neighbors_ort(x):
|
||||||
|
if g[nb] == ".":
|
||||||
|
nxs.append(nb)
|
||||||
|
elif g[nb] in LETTERS:
|
||||||
|
symbol = g[nb]
|
||||||
|
if symbol != start_symbol:
|
||||||
|
graph[symbol].add((start_symbol, steps + 1))
|
||||||
|
graph[start_symbol].add((symbol, steps + 1))
|
||||||
|
starts.append(nb)
|
||||||
|
xs = nxs
|
||||||
|
if len(xs) == 0:
|
||||||
|
break
|
||||||
|
|
||||||
|
all_keys = [g[p] for p in g.find(LETTERS_LOWER)]
|
||||||
|
poss = [(0, 0, tuple("@"), "@")]
|
||||||
|
best: dict[tuple[tuple, str], int] = {(("@",), "@"): 0}
|
||||||
|
|
||||||
|
min_dist = 10**9
|
||||||
|
while poss:
|
||||||
|
current_distance, key_count, keys, symbol = poss.pop()
|
||||||
|
# print(current_distance, key_count, keys, symbol)
|
||||||
|
|
||||||
|
if key_count - 1 == len(all_keys):
|
||||||
|
min_dist = min(min_dist, current_distance)
|
||||||
|
continue
|
||||||
|
|
||||||
|
for next_symbol, distance in graph[symbol]:
|
||||||
|
if next_symbol in LETTERS_UPPER and not next_symbol.lower() in keys:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if next_symbol in LETTERS_LOWER:
|
||||||
|
new_keys = set(keys)
|
||||||
|
new_keys.add(next_symbol)
|
||||||
|
new_keys = tuple(sorted(new_keys))
|
||||||
|
new_key_count = len(new_keys)
|
||||||
|
else:
|
||||||
|
new_keys = keys
|
||||||
|
new_key_count = key_count
|
||||||
|
|
||||||
|
new_distance = current_distance + distance
|
||||||
|
key = (new_keys, next_symbol)
|
||||||
|
if (key not in best) or (key in best and best[key] > new_distance):
|
||||||
|
best[key] = new_distance
|
||||||
|
poss.append((new_distance, new_key_count, new_keys, next_symbol))
|
||||||
|
print(min_dist)
|
||||||
|
|
||||||
|
|
||||||
|
def part_2(data):
|
||||||
|
g = Grid2D(data)
|
||||||
|
(start,) = g.find("@")
|
||||||
|
g[start] = "#"
|
||||||
|
g[add2(start, (-1, 0))] = "#"
|
||||||
|
g[add2(start, (1, 0))] = "#"
|
||||||
|
g[add2(start, (0, 1))] = "#"
|
||||||
|
g[add2(start, (0, -1))] = "#"
|
||||||
|
|
||||||
|
g[add2(start, (-1, -1))] = "0"
|
||||||
|
g[add2(start, (-1, 1))] = "1"
|
||||||
|
g[add2(start, (1, 1))] = "2"
|
||||||
|
g[add2(start, (1, -1))] = "3"
|
||||||
|
|
||||||
|
starts = g.find("0") + g.find("1") + g.find("2") + g.find("3")
|
||||||
|
graph = defaultdict(set)
|
||||||
|
starts_seen = set()
|
||||||
|
|
||||||
|
while starts:
|
||||||
|
start = starts.pop()
|
||||||
|
if start in starts_seen:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
starts_seen.add(start)
|
||||||
|
|
||||||
|
start_symbol = g[start]
|
||||||
|
xs = [start]
|
||||||
|
seen = set()
|
||||||
|
|
||||||
|
for steps in range(1_000):
|
||||||
|
nxs = []
|
||||||
|
|
||||||
|
for x in xs:
|
||||||
|
if x in seen:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
seen.add(x)
|
||||||
|
|
||||||
|
for nb in g.neighbors_ort(x):
|
||||||
|
if g[nb] == ".":
|
||||||
|
nxs.append(nb)
|
||||||
|
elif g[nb] in LETTERS:
|
||||||
|
symbol = g[nb]
|
||||||
|
if symbol != start_symbol:
|
||||||
|
graph[start].add((nb, steps + 1))
|
||||||
|
graph[nb].add((start, steps + 1))
|
||||||
|
starts.append(nb)
|
||||||
|
xs = nxs
|
||||||
|
if len(xs) == 0:
|
||||||
|
break
|
||||||
|
# g.print()
|
||||||
|
|
||||||
|
all_keys = [g[p] for p in g.find(LETTERS_LOWER)]
|
||||||
|
|
||||||
|
robots = tuple(g.find("0") + g.find("1") + g.find("2") + g.find("3"))
|
||||||
|
poss = [(0, tuple(), robots)]
|
||||||
|
best: dict[tuple[tuple, tuple], int] = {(tuple(), tuple()): 0}
|
||||||
|
|
||||||
|
min_dist = 10**9
|
||||||
|
while poss:
|
||||||
|
current_distance, keys, robots = poss.pop()
|
||||||
|
|
||||||
|
if len(keys) == len(all_keys):
|
||||||
|
min_dist = min(min_dist, current_distance)
|
||||||
|
# print(min_dist)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# print(current_distance, keys, robots)
|
||||||
|
for robot_i in range(len(robots)):
|
||||||
|
robot = robots[robot_i]
|
||||||
|
# robot_symbol = g[robot]
|
||||||
|
for next_pos, distance in graph[robot]:
|
||||||
|
next_symbol = g[next_pos]
|
||||||
|
|
||||||
|
if next_symbol in LETTERS_UPPER and not next_symbol.lower() in keys:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if next_symbol in LETTERS_LOWER:
|
||||||
|
new_keys = set(keys)
|
||||||
|
new_keys.add(next_symbol)
|
||||||
|
new_keys = tuple(sorted(new_keys))
|
||||||
|
else:
|
||||||
|
new_keys = keys
|
||||||
|
|
||||||
|
new_distance = current_distance + distance
|
||||||
|
|
||||||
|
new_robots = list(robots)
|
||||||
|
new_robots[robot_i] = next_pos
|
||||||
|
new_robots = tuple(new_robots)
|
||||||
|
|
||||||
|
key = (new_keys, new_robots)
|
||||||
|
if (key not in best) or (key in best and best[key] > new_distance):
|
||||||
|
best[key] = new_distance
|
||||||
|
poss.append((new_distance, new_keys, new_robots))
|
||||||
|
|
||||||
|
poss = sorted(poss, key=lambda xs: (xs[0], -len(xs[1])), reverse=True)
|
||||||
|
poss = poss[-10000:]
|
||||||
|
print(min_dist)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
data = get_data(__file__)
|
||||||
|
part_1(data)
|
||||||
|
part_2(data)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
154
2019/d20.py
Normal file
154
2019/d20.py
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
from lib import get_data, Grid2D, LETTERS_UPPER
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
data = get_data(__file__)
|
||||||
|
|
||||||
|
g = Grid2D(data)
|
||||||
|
|
||||||
|
start = None
|
||||||
|
end = None
|
||||||
|
warps = defaultdict(list)
|
||||||
|
|
||||||
|
inner = set()
|
||||||
|
outer = set()
|
||||||
|
|
||||||
|
for row in range(g.n_rows - 2):
|
||||||
|
for col in range(g.n_cols - 2):
|
||||||
|
a, b, c = g[(row, col)], g[(row, col + 1)], g[(row, col + 2)]
|
||||||
|
x, y, z = g[(row, col)], g[(row + 1, col)], g[(row + 2, col)]
|
||||||
|
if a in LETTERS_UPPER and b in LETTERS_UPPER and c == ".":
|
||||||
|
warps[a + b].append((row, col + 2))
|
||||||
|
if col == 0:
|
||||||
|
outer.add((row, col + 2))
|
||||||
|
else:
|
||||||
|
inner.add((row, col + 2))
|
||||||
|
elif a == "." and b in LETTERS_UPPER and c in LETTERS_UPPER:
|
||||||
|
warps[b + c].append((row, col))
|
||||||
|
if col + 3 == g.n_cols:
|
||||||
|
outer.add((row, col))
|
||||||
|
else:
|
||||||
|
inner.add((row, col))
|
||||||
|
|
||||||
|
if x in LETTERS_UPPER and y in LETTERS_UPPER and z == ".":
|
||||||
|
if x + y == "AA":
|
||||||
|
start = (row + 2, col)
|
||||||
|
else:
|
||||||
|
warps[x + y].append((row + 2, col))
|
||||||
|
if row == 0:
|
||||||
|
outer.add((row + 2, col))
|
||||||
|
else:
|
||||||
|
inner.add((row + 2, col))
|
||||||
|
elif x == "." and y in LETTERS_UPPER and z in LETTERS_UPPER:
|
||||||
|
if y + z == "ZZ":
|
||||||
|
end = (row, col)
|
||||||
|
else:
|
||||||
|
warps[y + z].append((row, col))
|
||||||
|
if row + 3 == g.n_rows:
|
||||||
|
outer.add((row, col))
|
||||||
|
else:
|
||||||
|
inner.add((row, col))
|
||||||
|
|
||||||
|
|
||||||
|
graph = defaultdict(list)
|
||||||
|
allnodes = set([start, end])
|
||||||
|
for key, (a, b) in warps.items():
|
||||||
|
graph[a].append((b, 1))
|
||||||
|
graph[b].append((a, 1))
|
||||||
|
allnodes.add(a)
|
||||||
|
allnodes.add(b)
|
||||||
|
|
||||||
|
for startnode in allnodes:
|
||||||
|
to_visit = [startnode]
|
||||||
|
steps = 0
|
||||||
|
seen = set()
|
||||||
|
while to_visit:
|
||||||
|
steps += 1
|
||||||
|
new_to_visit = []
|
||||||
|
for node in to_visit:
|
||||||
|
if node in seen:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
seen.add(node)
|
||||||
|
|
||||||
|
assert node is not None
|
||||||
|
for nb in g.neighbors_ort(node):
|
||||||
|
if nb in seen:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if nb in allnodes:
|
||||||
|
if not (nb, steps) in graph[startnode]:
|
||||||
|
graph[startnode].append((nb, steps))
|
||||||
|
if not (startnode, steps) in graph[nb]:
|
||||||
|
graph[nb].append((startnode, steps))
|
||||||
|
seen.add(nb)
|
||||||
|
elif g[nb] == ".":
|
||||||
|
new_to_visit.append(nb)
|
||||||
|
to_visit = new_to_visit
|
||||||
|
|
||||||
|
shortest = {start: 0}
|
||||||
|
to_visit = [start]
|
||||||
|
seen = set()
|
||||||
|
|
||||||
|
while to_visit:
|
||||||
|
to_visit.sort(key=lambda node: shortest[node], reverse=True)
|
||||||
|
current = to_visit.pop()
|
||||||
|
if current in seen:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
seen.add(current)
|
||||||
|
|
||||||
|
for nb, dist in graph[current]:
|
||||||
|
new_dist = shortest[current] + dist
|
||||||
|
if not nb in shortest:
|
||||||
|
shortest[nb] = new_dist
|
||||||
|
elif new_dist < shortest[nb]:
|
||||||
|
shortest[nb] = new_dist
|
||||||
|
if nb not in seen:
|
||||||
|
to_visit.append(nb)
|
||||||
|
|
||||||
|
print(shortest[end])
|
||||||
|
|
||||||
|
shortest = {(start, 0): 0}
|
||||||
|
to_visit = [(start, 0)]
|
||||||
|
seen = set()
|
||||||
|
|
||||||
|
while to_visit:
|
||||||
|
to_visit.sort(key=lambda node: shortest[node], reverse=True)
|
||||||
|
current, level = to_visit.pop()
|
||||||
|
|
||||||
|
if (current, level) in seen:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
seen.add((current, level))
|
||||||
|
|
||||||
|
for nb, dist in graph[current]:
|
||||||
|
new_dist = shortest[(current, level)] + dist
|
||||||
|
|
||||||
|
if nb == end and level == 0:
|
||||||
|
print(new_dist)
|
||||||
|
to_visit = None
|
||||||
|
break
|
||||||
|
elif nb == end:
|
||||||
|
continue
|
||||||
|
elif nb == start:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if dist == 1:
|
||||||
|
if current in inner:
|
||||||
|
new_level = level + 1
|
||||||
|
elif current in outer:
|
||||||
|
new_level = level - 1
|
||||||
|
else:
|
||||||
|
assert False
|
||||||
|
else:
|
||||||
|
new_level = level
|
||||||
|
|
||||||
|
if new_level < 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
nn = (nb, new_level)
|
||||||
|
if not nn in shortest:
|
||||||
|
shortest[nn] = new_dist
|
||||||
|
elif new_dist < shortest[nn]:
|
||||||
|
shortest[nn] = new_dist
|
||||||
|
to_visit.append(nn)
|
||||||
57
2019/d21.py
Normal file
57
2019/d21.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
from lib import get_data, str_to_ints
|
||||||
|
from d9 import Amp
|
||||||
|
|
||||||
|
data = get_data(__file__)
|
||||||
|
xs = str_to_ints(data)
|
||||||
|
|
||||||
|
script = """NOT A J
|
||||||
|
AND A J
|
||||||
|
NOT C T
|
||||||
|
AND D T
|
||||||
|
OR T J
|
||||||
|
NOT A T
|
||||||
|
OR T J
|
||||||
|
"""
|
||||||
|
|
||||||
|
a = Amp(xs)
|
||||||
|
while not a.done:
|
||||||
|
a.go()
|
||||||
|
if a.input_required:
|
||||||
|
for c in script:
|
||||||
|
a.feed(ord(c))
|
||||||
|
for c in "WALK\n":
|
||||||
|
a.feed(ord(c))
|
||||||
|
|
||||||
|
while a.outputs:
|
||||||
|
c = a.pop()
|
||||||
|
try:
|
||||||
|
chr(c)
|
||||||
|
# print(chr(c), end="")
|
||||||
|
except ValueError:
|
||||||
|
print(c)
|
||||||
|
|
||||||
|
script = """NOT C J
|
||||||
|
AND H J
|
||||||
|
NOT A T
|
||||||
|
OR T J
|
||||||
|
NOT B T
|
||||||
|
OR T J
|
||||||
|
AND D J
|
||||||
|
"""
|
||||||
|
|
||||||
|
a = Amp(xs)
|
||||||
|
while not a.done:
|
||||||
|
a.go()
|
||||||
|
if a.input_required:
|
||||||
|
for c in script:
|
||||||
|
a.feed(ord(c))
|
||||||
|
for c in "RUN\n":
|
||||||
|
a.feed(ord(c))
|
||||||
|
|
||||||
|
while a.outputs:
|
||||||
|
c = a.pop()
|
||||||
|
try:
|
||||||
|
chr(c)
|
||||||
|
# print(chr(c), end="")
|
||||||
|
except ValueError:
|
||||||
|
print(c)
|
||||||
118
2019/d22.py
Normal file
118
2019/d22.py
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
from lib import get_data, str_to_ints, mod_inverse
|
||||||
|
from math import gcd
|
||||||
|
|
||||||
|
data = get_data(__file__)
|
||||||
|
deck = list(range(10007))
|
||||||
|
|
||||||
|
# part 1
|
||||||
|
for line in data.splitlines():
|
||||||
|
if "new stack" in line:
|
||||||
|
deck = list(reversed(deck))
|
||||||
|
elif "cut" in line:
|
||||||
|
(n,) = str_to_ints(line)
|
||||||
|
deck = deck[n:] + deck[:n]
|
||||||
|
elif "increment" in line:
|
||||||
|
new_deck = [-1] * len(deck)
|
||||||
|
pos = 0
|
||||||
|
(n,) = str_to_ints(line)
|
||||||
|
deck = list(reversed(deck))
|
||||||
|
while deck:
|
||||||
|
new_deck[pos] = deck.pop()
|
||||||
|
pos = (pos + n) % len(new_deck)
|
||||||
|
deck = new_deck
|
||||||
|
else:
|
||||||
|
assert False
|
||||||
|
|
||||||
|
print(deck.index(2019))
|
||||||
|
|
||||||
|
len = 10007
|
||||||
|
orig_index = 2019
|
||||||
|
|
||||||
|
# figure out how to reverse...
|
||||||
|
index = orig_index
|
||||||
|
for line in data.splitlines():
|
||||||
|
if "new stack" in line:
|
||||||
|
new_index = len - (index + 1)
|
||||||
|
rev_index = -(new_index - len + 1)
|
||||||
|
assert rev_index == index
|
||||||
|
index = new_index
|
||||||
|
elif "cut" in line:
|
||||||
|
(cut,) = str_to_ints(line)
|
||||||
|
cut = (len + cut) % len
|
||||||
|
if index >= cut:
|
||||||
|
new_index = index - cut
|
||||||
|
else:
|
||||||
|
new_index = (len - cut) + index
|
||||||
|
rev_index = (new_index + cut) % len
|
||||||
|
assert rev_index == index
|
||||||
|
index = new_index
|
||||||
|
# calculate index from new_index and store in rev_index
|
||||||
|
elif "increment" in line:
|
||||||
|
(n,) = str_to_ints(line)
|
||||||
|
assert gcd(n, len) == 1
|
||||||
|
new_index = (n * index) % len
|
||||||
|
m = mod_inverse(n, len)
|
||||||
|
rev_index = (new_index * m) % len
|
||||||
|
assert rev_index == index
|
||||||
|
index = new_index
|
||||||
|
|
||||||
|
assert index == deck.index(2019)
|
||||||
|
|
||||||
|
# check that reverse approach works
|
||||||
|
for line in reversed(data.splitlines()):
|
||||||
|
if "new stack" in line:
|
||||||
|
index = -(index - len + 1)
|
||||||
|
elif "cut" in line:
|
||||||
|
(cut,) = str_to_ints(line)
|
||||||
|
cut = (len + cut) % len
|
||||||
|
index = (index + cut) % len
|
||||||
|
elif "increment" in line:
|
||||||
|
(n,) = str_to_ints(line)
|
||||||
|
assert gcd(n, len) == 1
|
||||||
|
m = mod_inverse(n, len)
|
||||||
|
index = (index * m) % len
|
||||||
|
assert index == orig_index
|
||||||
|
|
||||||
|
lines = list(reversed(data.splitlines()))
|
||||||
|
|
||||||
|
# new length of deck
|
||||||
|
len = 119315717514047
|
||||||
|
|
||||||
|
# get expression for one loop using sympy
|
||||||
|
from sympy import symbols, simplify
|
||||||
|
|
||||||
|
index = symbols("index")
|
||||||
|
expr = index
|
||||||
|
for line in lines:
|
||||||
|
if "new stack" in line:
|
||||||
|
# index = -(index - len + 1)
|
||||||
|
expr = -(expr - len + 1)
|
||||||
|
elif "cut" in line:
|
||||||
|
(cut,) = str_to_ints(line)
|
||||||
|
cut = (len + cut) % len
|
||||||
|
# index = (index + cut) % len
|
||||||
|
expr = expr + cut
|
||||||
|
elif "increment" in line:
|
||||||
|
(n,) = str_to_ints(line)
|
||||||
|
assert gcd(n, len) == 1
|
||||||
|
m = mod_inverse(n, len)
|
||||||
|
# index = (index * m) % len
|
||||||
|
expr = expr * m
|
||||||
|
|
||||||
|
# we can see that expression is in the form (a - b * i) % m
|
||||||
|
expr = simplify(expr % len)
|
||||||
|
coeff_dict = expr.args[0].as_coefficients_dict()
|
||||||
|
a = coeff_dict[1]
|
||||||
|
b = -coeff_dict[index]
|
||||||
|
|
||||||
|
# math
|
||||||
|
n_shuffles = 101741582076661
|
||||||
|
r0 = 2020
|
||||||
|
m = len
|
||||||
|
p = (-b) % m
|
||||||
|
p_t = pow(p, n_shuffles, m)
|
||||||
|
inv_b1 = mod_inverse(b + 1, m)
|
||||||
|
term1 = (p_t * r0) % m
|
||||||
|
term2 = (a * (1 - p_t) * inv_b1) % m
|
||||||
|
r_t = (term1 + term2) % m
|
||||||
|
print(r_t)
|
||||||
47
2019/d23.py
Normal file
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)
|
||||||
57
2020/d10.py
Normal file
57
2020/d10.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
from lib import get_data
|
||||||
|
from collections import defaultdict
|
||||||
|
from functools import lru_cache
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache
|
||||||
|
def count(xs, current, target):
|
||||||
|
delta_target = target - current
|
||||||
|
if len(xs) == 0:
|
||||||
|
if delta_target <= 3:
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
x = xs[0]
|
||||||
|
xs = xs[1:]
|
||||||
|
|
||||||
|
total = 0
|
||||||
|
delta = x - current
|
||||||
|
|
||||||
|
if delta > 3:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if delta <= 3:
|
||||||
|
total += count(xs, x, target)
|
||||||
|
|
||||||
|
total += count(xs, current, target)
|
||||||
|
return total
|
||||||
|
|
||||||
|
|
||||||
|
def part_1(data):
|
||||||
|
xs = list(map(int, data.strip().splitlines()))
|
||||||
|
out = max(xs) + 3
|
||||||
|
ds = defaultdict(int)
|
||||||
|
|
||||||
|
c = 0
|
||||||
|
for x in sorted(xs):
|
||||||
|
d = x - c
|
||||||
|
ds[d] += 1
|
||||||
|
c = x
|
||||||
|
d = out - c
|
||||||
|
ds[d] += 1
|
||||||
|
|
||||||
|
a, b = list(ds.values())
|
||||||
|
print(a * b)
|
||||||
|
|
||||||
|
xs = tuple(sorted(xs))
|
||||||
|
print(count(xs, 0, out))
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
data = get_data(__file__)
|
||||||
|
part_1(data)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
89
2020/d11.py
Normal file
89
2020/d11.py
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
from lib import get_data, Grid2D, add2
|
||||||
|
|
||||||
|
data = """L.LL.LL.LL
|
||||||
|
LLLLLLL.LL
|
||||||
|
L.L.L..L..
|
||||||
|
LLLL.LL.LL
|
||||||
|
L.LL.LL.LL
|
||||||
|
L.LLLLL.LL
|
||||||
|
..L.L.....
|
||||||
|
LLLLLLLLLL
|
||||||
|
L.LLLLLL.L
|
||||||
|
L.LLLLL.LL"""
|
||||||
|
|
||||||
|
|
||||||
|
def part_1(data):
|
||||||
|
g = Grid2D(data)
|
||||||
|
seen = set()
|
||||||
|
while True:
|
||||||
|
h = g.hash()
|
||||||
|
if h in seen:
|
||||||
|
break
|
||||||
|
seen.add(h)
|
||||||
|
|
||||||
|
ng = g.clone_with_val(".")
|
||||||
|
for r in range(g.n_rows):
|
||||||
|
for c in range(g.n_cols):
|
||||||
|
p = (r, c)
|
||||||
|
s = g[p]
|
||||||
|
if s == ".":
|
||||||
|
continue
|
||||||
|
occupied = sum([1 if g[nb] == "#" else 0 for nb in g.neighbors_adj(p)])
|
||||||
|
if s == "L" and occupied == 0:
|
||||||
|
ng[p] = "#"
|
||||||
|
elif s == "#" and occupied >= 4:
|
||||||
|
ng[p] = "L"
|
||||||
|
else:
|
||||||
|
ng[p] = s
|
||||||
|
g = ng
|
||||||
|
print(len(g.find("#")))
|
||||||
|
|
||||||
|
|
||||||
|
def part_2(data):
|
||||||
|
g = Grid2D(data)
|
||||||
|
seen = set()
|
||||||
|
while True:
|
||||||
|
h = g.hash()
|
||||||
|
if h in seen:
|
||||||
|
break
|
||||||
|
seen.add(h)
|
||||||
|
|
||||||
|
ng = g.clone_with_val(".")
|
||||||
|
for r in range(g.n_rows):
|
||||||
|
for c in range(g.n_cols):
|
||||||
|
p = (r, c)
|
||||||
|
s = g[p]
|
||||||
|
if s == ".":
|
||||||
|
continue
|
||||||
|
|
||||||
|
occupied = 0
|
||||||
|
for dir in g.COORDS_ORTH + g.COORDS_DIAG:
|
||||||
|
np = p
|
||||||
|
while True:
|
||||||
|
np = add2(np, dir)
|
||||||
|
if not g.contains(np):
|
||||||
|
break
|
||||||
|
elif g[np] == "#":
|
||||||
|
occupied += 1
|
||||||
|
break
|
||||||
|
elif g[np] == "L":
|
||||||
|
break
|
||||||
|
|
||||||
|
if s == "L" and occupied == 0:
|
||||||
|
ng[p] = "#"
|
||||||
|
elif s == "#" and occupied >= 5:
|
||||||
|
ng[p] = "L"
|
||||||
|
else:
|
||||||
|
ng[p] = s
|
||||||
|
g = ng
|
||||||
|
print(len(g.find("#")))
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
data = get_data(__file__)
|
||||||
|
part_1(data)
|
||||||
|
part_2(data)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
82
2020/d12.py
Normal file
82
2020/d12.py
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
from lib import get_data
|
||||||
|
|
||||||
|
DIRS = {
|
||||||
|
"N": (-1, 0),
|
||||||
|
"E": (0, 1),
|
||||||
|
"S": (1, 0),
|
||||||
|
"W": (0, -1),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def part_1(data):
|
||||||
|
dir_key = "E"
|
||||||
|
pos = 0, 0
|
||||||
|
dirs = "NESW"
|
||||||
|
|
||||||
|
for line in data.splitlines():
|
||||||
|
c = line[0]
|
||||||
|
v = int(line[1:])
|
||||||
|
if c in DIRS.keys():
|
||||||
|
pos = pos[0] + v * DIRS[c][0], pos[1] + v * DIRS[c][1]
|
||||||
|
elif c == "F":
|
||||||
|
dir = DIRS[dir_key]
|
||||||
|
pos = (pos[0] + v * dir[0], pos[1] + v * dir[1])
|
||||||
|
elif c == "R":
|
||||||
|
assert v % 90 == 0
|
||||||
|
dir_key = dirs[(dirs.index(dir_key) + v // 90) % len(dirs)]
|
||||||
|
elif c == "L":
|
||||||
|
assert v % 90 == 0
|
||||||
|
dir_key = dirs[(dirs.index(dir_key) - v // 90) % len(dirs)]
|
||||||
|
else:
|
||||||
|
print(c, v)
|
||||||
|
assert False
|
||||||
|
print(sum(map(abs, pos)))
|
||||||
|
|
||||||
|
|
||||||
|
def part_2(data):
|
||||||
|
way_point = (-1, 10)
|
||||||
|
pos = 0, 0
|
||||||
|
|
||||||
|
for line in data.splitlines():
|
||||||
|
c = line[0]
|
||||||
|
v = int(line[1:])
|
||||||
|
if c in DIRS.keys():
|
||||||
|
way_point = way_point[0] + v * DIRS[c][0], way_point[1] + v * DIRS[c][1]
|
||||||
|
elif c == "F":
|
||||||
|
pos = (pos[0] + v * way_point[0], pos[1] + v * way_point[1])
|
||||||
|
elif c == "R":
|
||||||
|
row, col = way_point
|
||||||
|
if v == 90:
|
||||||
|
way_point = (col, -row)
|
||||||
|
elif v == 180:
|
||||||
|
way_point = (-row, -col)
|
||||||
|
elif v == 270:
|
||||||
|
way_point = (-col, row)
|
||||||
|
else:
|
||||||
|
print(c, v)
|
||||||
|
assert False
|
||||||
|
elif c == "L":
|
||||||
|
row, col = way_point
|
||||||
|
if v == 90:
|
||||||
|
way_point = (-col, row)
|
||||||
|
elif v == 180:
|
||||||
|
way_point = (-row, -col)
|
||||||
|
elif v == 270:
|
||||||
|
way_point = (col, -row)
|
||||||
|
else:
|
||||||
|
print(c, v)
|
||||||
|
assert False
|
||||||
|
else:
|
||||||
|
print(c, v)
|
||||||
|
assert False
|
||||||
|
print(sum(map(abs, pos)))
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
data = get_data(__file__)
|
||||||
|
part_1(data)
|
||||||
|
part_2(data)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
39
2020/d13.py
Normal file
39
2020/d13.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
from lib import get_data, str_to_ints, mod_inverse
|
||||||
|
from functools import reduce
|
||||||
|
|
||||||
|
data = get_data(__file__)
|
||||||
|
lines = data.splitlines()
|
||||||
|
|
||||||
|
earliest = int(lines[0])
|
||||||
|
times = str_to_ints(lines[1])
|
||||||
|
|
||||||
|
mintime = 10**21
|
||||||
|
id = None
|
||||||
|
|
||||||
|
for time in times:
|
||||||
|
mintimecur = (earliest // time) * time + time
|
||||||
|
if mintimecur < mintime:
|
||||||
|
mintime = mintimecur
|
||||||
|
id = time
|
||||||
|
|
||||||
|
|
||||||
|
assert id is not None
|
||||||
|
print((mintime - earliest) * id)
|
||||||
|
|
||||||
|
fields = lines[1].split(",")
|
||||||
|
|
||||||
|
buses = []
|
||||||
|
for offset, busid in enumerate(fields):
|
||||||
|
if busid == "x":
|
||||||
|
continue
|
||||||
|
period = int(busid)
|
||||||
|
buses.append((period, -offset % period))
|
||||||
|
|
||||||
|
|
||||||
|
total = 0
|
||||||
|
product = reduce(lambda a, b: a * b, map(lambda x: x[0], buses))
|
||||||
|
for period, offset in buses:
|
||||||
|
p = product // period
|
||||||
|
total += offset * mod_inverse(p, period) * p
|
||||||
|
|
||||||
|
print(total % product)
|
||||||
77
2020/d14.py
Normal file
77
2020/d14.py
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
from lib import get_data, str_to_ints
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
data = """mask = XXXXXXXXXXXXXXXXXXXXXXXXXXXXX1XXXX0X
|
||||||
|
mem[8] = 11
|
||||||
|
mem[7] = 101
|
||||||
|
mem[8] = 0
|
||||||
|
"""
|
||||||
|
data = get_data(__file__)
|
||||||
|
|
||||||
|
mem = defaultdict(int)
|
||||||
|
mfix, mopt = None, None
|
||||||
|
for line in data.splitlines():
|
||||||
|
if line.startswith("mask"):
|
||||||
|
fix = "0b"
|
||||||
|
opt = "0b"
|
||||||
|
for c in line[7:]:
|
||||||
|
if c == "1":
|
||||||
|
fix += "1"
|
||||||
|
opt += "0"
|
||||||
|
elif c == "0":
|
||||||
|
fix += "0"
|
||||||
|
opt += "0"
|
||||||
|
elif c == "X":
|
||||||
|
fix += "0"
|
||||||
|
opt += "1"
|
||||||
|
else:
|
||||||
|
assert False
|
||||||
|
mfix = int(fix, 2)
|
||||||
|
mopt = int(opt, 2)
|
||||||
|
elif line.startswith("mem"):
|
||||||
|
assert mfix is not None and mopt is not None
|
||||||
|
addr, value = str_to_ints(line)
|
||||||
|
value = mfix | (value & mopt)
|
||||||
|
mem[addr] = value
|
||||||
|
|
||||||
|
print(sum(v for v in mem.values()))
|
||||||
|
|
||||||
|
mem = defaultdict(int)
|
||||||
|
masks = []
|
||||||
|
for line in data.splitlines():
|
||||||
|
if line.startswith("mask"):
|
||||||
|
fix = ["0b"]
|
||||||
|
opt = ["0b"]
|
||||||
|
for c in line[7:]:
|
||||||
|
if c == "0":
|
||||||
|
fix = [f + "0" for f in fix]
|
||||||
|
opt = [o + "1" for o in opt]
|
||||||
|
elif c == "1":
|
||||||
|
fix = [f + "1" for f in fix]
|
||||||
|
opt = [o + "0" for o in opt]
|
||||||
|
elif c == "X":
|
||||||
|
nfix = []
|
||||||
|
nopt = []
|
||||||
|
for f in fix:
|
||||||
|
nfix.append(f + "0")
|
||||||
|
nfix.append(f + "1")
|
||||||
|
for o in opt:
|
||||||
|
nopt.append(o + "0")
|
||||||
|
nopt.append(o + "0")
|
||||||
|
fix = nfix
|
||||||
|
opt = nopt
|
||||||
|
else:
|
||||||
|
assert False
|
||||||
|
masks = tuple(
|
||||||
|
zip(
|
||||||
|
tuple(map(lambda f: int(f, 2), fix)),
|
||||||
|
tuple(map(lambda o: int(o, 2), opt)),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif line.startswith("mem"):
|
||||||
|
addr, value = str_to_ints(line)
|
||||||
|
for fix, opt in masks:
|
||||||
|
addr_ = fix | (addr & opt)
|
||||||
|
mem[addr_] = value
|
||||||
|
|
||||||
|
print(sum(v for v in mem.values()))
|
||||||
22
2020/d15.py
Normal file
22
2020/d15.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
from lib import get_data, str_to_ints
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
data = get_data(__file__)
|
||||||
|
|
||||||
|
for limit in [2021, 30000001]:
|
||||||
|
xs = list(reversed(str_to_ints(data)))
|
||||||
|
spoken = defaultdict(list)
|
||||||
|
recent = None
|
||||||
|
for turn in range(1, limit):
|
||||||
|
if len(xs) > 0:
|
||||||
|
recent = xs.pop()
|
||||||
|
spoken[recent].append(turn)
|
||||||
|
else:
|
||||||
|
if recent in spoken and len(spoken[recent]) == 1:
|
||||||
|
recent = 0
|
||||||
|
elif recent in spoken:
|
||||||
|
recent = spoken[recent][-1] - spoken[recent][-2]
|
||||||
|
else:
|
||||||
|
recent = 0
|
||||||
|
spoken[recent].append(turn)
|
||||||
|
print(recent)
|
||||||
88
2020/d16.py
Normal file
88
2020/d16.py
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
from lib import get_data, str_to_ints
|
||||||
|
|
||||||
|
data = get_data(__file__)
|
||||||
|
|
||||||
|
dps = []
|
||||||
|
ranges = []
|
||||||
|
lines = (l for l in data.splitlines())
|
||||||
|
|
||||||
|
index = 0
|
||||||
|
for line in lines:
|
||||||
|
if line.strip() == "":
|
||||||
|
break
|
||||||
|
if line.startswith("departure"):
|
||||||
|
dps.append(index)
|
||||||
|
a, b, c, d = str_to_ints(line.replace("-", " "))
|
||||||
|
# ranges.append(((a, b), (c, d)))
|
||||||
|
ranges.append((a, b, c, d))
|
||||||
|
index += 1
|
||||||
|
|
||||||
|
next(lines)
|
||||||
|
my_ticket = str_to_ints(next(lines))
|
||||||
|
next(lines)
|
||||||
|
next(lines)
|
||||||
|
|
||||||
|
error = 0
|
||||||
|
valid = []
|
||||||
|
fields = []
|
||||||
|
for line in lines:
|
||||||
|
# print(line)
|
||||||
|
has_error = False
|
||||||
|
field = []
|
||||||
|
for x in str_to_ints(line):
|
||||||
|
no_match = True
|
||||||
|
field.append(set())
|
||||||
|
for i, (a, b, c, d) in enumerate(ranges):
|
||||||
|
if a <= x <= b:
|
||||||
|
field[-1].add(i)
|
||||||
|
no_match = False
|
||||||
|
elif c <= x <= d:
|
||||||
|
field[-1].add(i)
|
||||||
|
no_match = False
|
||||||
|
if no_match:
|
||||||
|
has_error = True
|
||||||
|
error += x
|
||||||
|
if not has_error:
|
||||||
|
valid.append(line)
|
||||||
|
fields.append(field)
|
||||||
|
|
||||||
|
base = fields[0]
|
||||||
|
for field in fields[1:]:
|
||||||
|
for i in range(len(field)):
|
||||||
|
base[i] &= field[i]
|
||||||
|
print(error)
|
||||||
|
|
||||||
|
used = set()
|
||||||
|
done = False
|
||||||
|
base = [list(xs) for xs in base]
|
||||||
|
while not done:
|
||||||
|
done = True
|
||||||
|
single = None
|
||||||
|
for xs in base:
|
||||||
|
if len(xs) == 1 and xs[0] not in used:
|
||||||
|
single = xs[0]
|
||||||
|
used.add(single)
|
||||||
|
break
|
||||||
|
|
||||||
|
if single is not None:
|
||||||
|
for xs in base:
|
||||||
|
if len(xs) == 1:
|
||||||
|
continue
|
||||||
|
if single in xs:
|
||||||
|
xs.remove(single)
|
||||||
|
|
||||||
|
for xs in base:
|
||||||
|
if len(xs) != 1:
|
||||||
|
done = False
|
||||||
|
break
|
||||||
|
|
||||||
|
mapping = []
|
||||||
|
for xs in base:
|
||||||
|
(x,) = xs
|
||||||
|
mapping.append(x)
|
||||||
|
|
||||||
|
r = 1
|
||||||
|
for i, v in zip(mapping, my_ticket):
|
||||||
|
if i in dps:
|
||||||
|
r *= v
|
||||||
|
print(r)
|
||||||
42
2020/d17.py
Normal file
42
2020/d17.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
from itertools import product
|
||||||
|
from lib import get_data
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
data = get_data(__file__)
|
||||||
|
|
||||||
|
|
||||||
|
def neighbors(p):
|
||||||
|
d = len(p)
|
||||||
|
for xs in product(range(-1, 2), repeat=d):
|
||||||
|
if all(x == 0 for x in xs):
|
||||||
|
continue
|
||||||
|
yield tuple([p[i] + xs[i] for i in range(len(xs))])
|
||||||
|
|
||||||
|
|
||||||
|
for d in [3, 4]:
|
||||||
|
points = set()
|
||||||
|
lines = data.splitlines()
|
||||||
|
for y in range(len(lines)):
|
||||||
|
for x in range(len(lines[0])):
|
||||||
|
if lines[y][x] == "#":
|
||||||
|
points.add(tuple([x, y] + [0] * (d - 2)))
|
||||||
|
|
||||||
|
for _ in range(6):
|
||||||
|
new_points = set()
|
||||||
|
actives = defaultdict(int)
|
||||||
|
for p in points:
|
||||||
|
nbcount = 0
|
||||||
|
for nb in neighbors(p):
|
||||||
|
actives[nb] += 1
|
||||||
|
if nb in points:
|
||||||
|
nbcount += 1
|
||||||
|
if nbcount == 2 or nbcount == 3:
|
||||||
|
new_points.add(p)
|
||||||
|
for p, count in actives.items():
|
||||||
|
if p in points:
|
||||||
|
continue
|
||||||
|
elif count == 3:
|
||||||
|
new_points.add(p)
|
||||||
|
points = new_points
|
||||||
|
|
||||||
|
print(len(points))
|
||||||
106
2020/d18.py
Normal file
106
2020/d18.py
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
from lib import get_data
|
||||||
|
|
||||||
|
|
||||||
|
def eval_1(s) -> int:
|
||||||
|
if type(s) is int:
|
||||||
|
return s
|
||||||
|
i = 0
|
||||||
|
parts = []
|
||||||
|
while i < len(s):
|
||||||
|
if s[i] == "(":
|
||||||
|
nested = 1
|
||||||
|
r = "("
|
||||||
|
i += 1
|
||||||
|
while nested > 0:
|
||||||
|
r += s[i]
|
||||||
|
if s[i] == ")":
|
||||||
|
nested -= 1
|
||||||
|
elif s[i] == "(":
|
||||||
|
nested += 1
|
||||||
|
i += 1
|
||||||
|
parts.append(r[1:-1])
|
||||||
|
elif s[i].isdigit():
|
||||||
|
d = ""
|
||||||
|
while i < len(s) and s[i].isdigit():
|
||||||
|
d += s[i]
|
||||||
|
i += 1
|
||||||
|
parts.append(int(d))
|
||||||
|
elif s[i] == " ":
|
||||||
|
pass
|
||||||
|
elif s[i] == "*" or s[i] == "+":
|
||||||
|
parts.append(s[i])
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
while len(parts) > 1:
|
||||||
|
if parts[1] == "*":
|
||||||
|
parts[2] = eval_1(parts[0]) * eval_1(parts[2])
|
||||||
|
parts = parts[2:]
|
||||||
|
elif parts[1] == "+":
|
||||||
|
parts[2] = eval_1(parts[0]) + eval_1(parts[2])
|
||||||
|
parts = parts[2:]
|
||||||
|
else:
|
||||||
|
assert False
|
||||||
|
return parts[-1]
|
||||||
|
|
||||||
|
|
||||||
|
def eval_2(s) -> int:
|
||||||
|
if type(s) is int:
|
||||||
|
return s
|
||||||
|
i = 0
|
||||||
|
parts = []
|
||||||
|
while i < len(s):
|
||||||
|
if s[i] == "(":
|
||||||
|
nested = 1
|
||||||
|
r = "("
|
||||||
|
i += 1
|
||||||
|
while nested > 0:
|
||||||
|
r += s[i]
|
||||||
|
if s[i] == ")":
|
||||||
|
nested -= 1
|
||||||
|
elif s[i] == "(":
|
||||||
|
nested += 1
|
||||||
|
i += 1
|
||||||
|
parts.append(r[1:-1])
|
||||||
|
elif s[i].isdigit():
|
||||||
|
d = ""
|
||||||
|
while i < len(s) and s[i].isdigit():
|
||||||
|
d += s[i]
|
||||||
|
i += 1
|
||||||
|
parts.append(int(d))
|
||||||
|
elif s[i] == " ":
|
||||||
|
pass
|
||||||
|
elif s[i] == "*" or s[i] == "+":
|
||||||
|
parts.append(s[i])
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
while len(parts) > 1:
|
||||||
|
new_parts = []
|
||||||
|
for i in range(0, len(parts) - 1, 2):
|
||||||
|
if parts[i + 1] == "+":
|
||||||
|
new_parts.append(eval_2(parts[i]) + eval_2(parts[i + 2]))
|
||||||
|
new_parts += parts[i + 3 :]
|
||||||
|
break
|
||||||
|
elif parts[1] == "*":
|
||||||
|
new_parts.append(parts[i])
|
||||||
|
new_parts.append(parts[i + 1])
|
||||||
|
else:
|
||||||
|
assert False
|
||||||
|
else:
|
||||||
|
assert parts[1] == "*"
|
||||||
|
new_parts = [eval_2(parts[0]) * eval_2(parts[2])]
|
||||||
|
new_parts += parts[3:]
|
||||||
|
parts = new_parts
|
||||||
|
return parts[-1]
|
||||||
|
|
||||||
|
|
||||||
|
data = get_data(__file__)
|
||||||
|
|
||||||
|
t1, t2 = 0, 0
|
||||||
|
for line in data.splitlines():
|
||||||
|
t1 += eval_1(line)
|
||||||
|
t2 += eval_2(line)
|
||||||
|
|
||||||
|
print(t1)
|
||||||
|
print(t2)
|
||||||
|
assert t2 == 290726428573651
|
||||||
62
2020/d19.py
Normal file
62
2020/d19.py
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
from lib import get_data
|
||||||
|
|
||||||
|
data = get_data(__file__)
|
||||||
|
|
||||||
|
rules = {}
|
||||||
|
msgs = []
|
||||||
|
|
||||||
|
for line in data.splitlines():
|
||||||
|
if len(line.strip()) == 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if ":" in line:
|
||||||
|
id, rule = line.split(":")
|
||||||
|
id = int(id)
|
||||||
|
|
||||||
|
branches = [[]]
|
||||||
|
for part in rule.split():
|
||||||
|
if part.startswith('"') and part.endswith('"'):
|
||||||
|
branches = part[1]
|
||||||
|
elif part == "|":
|
||||||
|
assert type(branches) is list
|
||||||
|
branches.append([])
|
||||||
|
else:
|
||||||
|
assert type(branches) is list
|
||||||
|
branches[-1].append(int(part))
|
||||||
|
rules[id] = branches
|
||||||
|
else:
|
||||||
|
msgs.append(line.strip())
|
||||||
|
|
||||||
|
|
||||||
|
def matches(xs, rule):
|
||||||
|
if xs == "" and rule == []:
|
||||||
|
return True
|
||||||
|
elif xs == "" or rule == []:
|
||||||
|
return False
|
||||||
|
|
||||||
|
current = rules[rule[0]]
|
||||||
|
if current == xs[0]:
|
||||||
|
return matches(xs[1:], rule[1:])
|
||||||
|
elif type(current) is str:
|
||||||
|
return False
|
||||||
|
elif len(current) >= 1:
|
||||||
|
for ys in current:
|
||||||
|
if matches(xs, ys + rule[1:]):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
t = 0
|
||||||
|
for m in msgs:
|
||||||
|
if matches(m, rules[0][0]):
|
||||||
|
t += 1
|
||||||
|
print(t)
|
||||||
|
|
||||||
|
rules[8] = [[42], [42, 8]]
|
||||||
|
rules[11] = [[42, 31], [42, 11, 31]]
|
||||||
|
|
||||||
|
t = 0
|
||||||
|
for m in msgs:
|
||||||
|
if matches(m, rules[0][0]):
|
||||||
|
t += 1
|
||||||
|
print(t)
|
||||||
169
2020/d20.py
Normal file
169
2020/d20.py
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
from collections import defaultdict
|
||||||
|
from lib import get_data, ints
|
||||||
|
from math import isqrt
|
||||||
|
|
||||||
|
|
||||||
|
data = get_data(__file__)
|
||||||
|
|
||||||
|
|
||||||
|
def top(rows):
|
||||||
|
return tuple(rows[0])
|
||||||
|
|
||||||
|
|
||||||
|
def bottom(rows):
|
||||||
|
return tuple(rows[-1])
|
||||||
|
|
||||||
|
|
||||||
|
def left(rows):
|
||||||
|
return tuple(tuple(row[0] for row in rows))
|
||||||
|
|
||||||
|
|
||||||
|
def right(rows):
|
||||||
|
return tuple(tuple(row[-1] for row in rows))
|
||||||
|
|
||||||
|
|
||||||
|
def fliph(rows):
|
||||||
|
return tuple(tuple(row[::-1] for row in rows))
|
||||||
|
|
||||||
|
|
||||||
|
def flipv(rows):
|
||||||
|
return tuple(rows[::-1])
|
||||||
|
|
||||||
|
|
||||||
|
def rot90(rows):
|
||||||
|
return tuple(tuple(row) for row in zip(*rows[::-1]))
|
||||||
|
|
||||||
|
|
||||||
|
def rot180(rows):
|
||||||
|
return rot90(rot90(rows))
|
||||||
|
|
||||||
|
|
||||||
|
def rot270(rows):
|
||||||
|
return rot90(rot90(rot90(rows)))
|
||||||
|
|
||||||
|
|
||||||
|
TOP = 0
|
||||||
|
RIGHT = 1
|
||||||
|
BOTTOM = 2
|
||||||
|
LEFT = 3
|
||||||
|
|
||||||
|
|
||||||
|
def all(rows) -> list:
|
||||||
|
return [
|
||||||
|
rows,
|
||||||
|
rot90(rows),
|
||||||
|
rot180(rows),
|
||||||
|
rot270(rows),
|
||||||
|
fliph(rows),
|
||||||
|
flipv(rows),
|
||||||
|
rot90(fliph(rows)),
|
||||||
|
rot90(flipv(rows)),
|
||||||
|
# rot180(fliph(rows)),
|
||||||
|
# rot180(flipv(rows)),
|
||||||
|
# rot270(fliph(rows)),
|
||||||
|
# rot270(flipv(rows)),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
tiles = []
|
||||||
|
for p in data.strip().split("\n\n"):
|
||||||
|
lines = p.splitlines()
|
||||||
|
(id,) = ints(lines[0])
|
||||||
|
rowst = tuple(map(tuple, lines[1:]))
|
||||||
|
tiles.append((id, all(rowst)))
|
||||||
|
|
||||||
|
|
||||||
|
rights = defaultdict(list)
|
||||||
|
bottoms = defaultdict(list)
|
||||||
|
|
||||||
|
for id, variants in tiles:
|
||||||
|
for variant in variants:
|
||||||
|
rights[left(variant)].append((id, variant))
|
||||||
|
bottoms[top(variant)].append((id, variant))
|
||||||
|
|
||||||
|
num_tiles = len(tiles)
|
||||||
|
rows = isqrt(len(tiles))
|
||||||
|
|
||||||
|
|
||||||
|
def dfs(id_set, id_list, tiles_used):
|
||||||
|
if len(tiles_used) == num_tiles:
|
||||||
|
return id_list, tiles_used
|
||||||
|
|
||||||
|
i = len(tiles_used)
|
||||||
|
id_variants = None
|
||||||
|
if i < rows: # first row
|
||||||
|
id_variants = rights[right(tiles_used[i - 1])]
|
||||||
|
elif i % rows == 0: # first tile in row
|
||||||
|
id_variants = bottoms[bottom(tiles_used[i - rows])]
|
||||||
|
else:
|
||||||
|
id_variants = set(bottoms[bottom(tiles_used[i - rows])])
|
||||||
|
id_variants &= set(rights[right(tiles_used[i - 1])])
|
||||||
|
id_variants = list(id_variants)
|
||||||
|
|
||||||
|
assert id_variants is not None
|
||||||
|
|
||||||
|
for id, variant in id_variants:
|
||||||
|
if id in id_set:
|
||||||
|
continue
|
||||||
|
id_set.add(id)
|
||||||
|
id_list.append(id)
|
||||||
|
tiles_used.append(variant)
|
||||||
|
r = dfs(id_set, id_list, tiles_used)
|
||||||
|
if r is not False:
|
||||||
|
return r
|
||||||
|
tiles_used.pop()
|
||||||
|
id_set.remove(id)
|
||||||
|
id_list.pop()
|
||||||
|
return False, None
|
||||||
|
|
||||||
|
|
||||||
|
tiles_used = None
|
||||||
|
for id, tile_variants in tiles:
|
||||||
|
for tile in tile_variants:
|
||||||
|
r, tiles_used = dfs(set([id]), [id], [tile])
|
||||||
|
if type(r) is list:
|
||||||
|
print(r[0] * r[-1] * r[rows - 1] * r[-rows])
|
||||||
|
break
|
||||||
|
if tiles_used is not None:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print("no result")
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
tiles_used = [[tile[x][1:-1] for x in range(1, len(tile) - 1)] for tile in tiles_used]
|
||||||
|
|
||||||
|
|
||||||
|
merged_tiles = []
|
||||||
|
for i in range(0, rows * rows, rows):
|
||||||
|
for row in range(len(tiles_used[0])):
|
||||||
|
merged_row = []
|
||||||
|
for j in range(rows):
|
||||||
|
merged_row.extend(tiles_used[i + j][row])
|
||||||
|
merged_tiles.append(merged_row)
|
||||||
|
|
||||||
|
seemonster = """ #
|
||||||
|
# ## ## ###
|
||||||
|
# # # # # # """
|
||||||
|
|
||||||
|
offsets: list[tuple[int, int]] = []
|
||||||
|
for row, line in enumerate(seemonster.splitlines()):
|
||||||
|
for col, c in enumerate(line):
|
||||||
|
if c == "#":
|
||||||
|
offsets.append((row, col))
|
||||||
|
|
||||||
|
total_hashes = sum([row.count("#") for row in merged_tiles])
|
||||||
|
seemonster_hashes = len(offsets)
|
||||||
|
|
||||||
|
max_seemonster_count = 0
|
||||||
|
for field in all(merged_tiles):
|
||||||
|
seemonster_count = 0
|
||||||
|
for ri in range(len(field) - 2):
|
||||||
|
for ci in range(len(field[0]) - 19):
|
||||||
|
for ro, co in offsets:
|
||||||
|
if field[ri + ro][ci + co] != "#":
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
seemonster_count += 1
|
||||||
|
max_seemonster_count = max(max_seemonster_count, seemonster_count)
|
||||||
|
|
||||||
|
print(total_hashes - max_seemonster_count * seemonster_hashes)
|
||||||
50
2020/d21.py
Normal file
50
2020/d21.py
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
from collections import defaultdict
|
||||||
|
from lib import get_data
|
||||||
|
|
||||||
|
data = get_data(__file__)
|
||||||
|
|
||||||
|
algs_to_ings = defaultdict(set)
|
||||||
|
ings_counts = defaultdict(int)
|
||||||
|
|
||||||
|
for line in data.splitlines():
|
||||||
|
a, b = line.split(" (contains ")
|
||||||
|
ings = a.split(" ")
|
||||||
|
algs = b[:-1].split(", ")
|
||||||
|
for ing in ings:
|
||||||
|
ings_counts[ing] += 1
|
||||||
|
|
||||||
|
for a in algs:
|
||||||
|
if a not in algs_to_ings:
|
||||||
|
algs_to_ings[a] = set(ings)
|
||||||
|
else:
|
||||||
|
algs_to_ings[a] &= set(ings)
|
||||||
|
|
||||||
|
|
||||||
|
for ings in algs_to_ings.values():
|
||||||
|
for ing in ings:
|
||||||
|
if ing in ings_counts:
|
||||||
|
del ings_counts[ing]
|
||||||
|
t = sum(ings_counts.values())
|
||||||
|
print(t)
|
||||||
|
|
||||||
|
|
||||||
|
algs_to_ings = {a: list(ing) for a, ing in algs_to_ings.items()}
|
||||||
|
|
||||||
|
handled = set()
|
||||||
|
while True:
|
||||||
|
to_remove = None
|
||||||
|
for alg, ings in algs_to_ings.items():
|
||||||
|
if len(ings) == 1 and ings[0] not in handled:
|
||||||
|
to_remove = ings[0]
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
assert to_remove is not None
|
||||||
|
handled.add(to_remove)
|
||||||
|
for ings in algs_to_ings.values():
|
||||||
|
if len(ings) > 1 and to_remove in ings:
|
||||||
|
ings.remove(to_remove)
|
||||||
|
|
||||||
|
xs = sorted([(a, i[0]) for a, i in algs_to_ings.items()])
|
||||||
|
xs = ",".join([x[1] for x in xs])
|
||||||
|
print(xs)
|
||||||
86
2020/d22.py
Normal file
86
2020/d22.py
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
from collections import deque, defaultdict
|
||||||
|
from lib import get_data, ints
|
||||||
|
|
||||||
|
data = get_data(__file__)
|
||||||
|
|
||||||
|
a, b = data.strip().split("\n\n")
|
||||||
|
a = deque(ints(a)[1:])
|
||||||
|
b = deque(ints(b)[1:])
|
||||||
|
|
||||||
|
while a and b:
|
||||||
|
x, y = a.popleft(), b.popleft()
|
||||||
|
if x > y:
|
||||||
|
a.append(x)
|
||||||
|
a.append(y)
|
||||||
|
elif y > x:
|
||||||
|
b.append(y)
|
||||||
|
b.append(x)
|
||||||
|
else:
|
||||||
|
assert False
|
||||||
|
|
||||||
|
|
||||||
|
for p in [a, b]:
|
||||||
|
s = 0
|
||||||
|
for i, c in enumerate(reversed(p)):
|
||||||
|
s += (i + 1) * c
|
||||||
|
if s > 0:
|
||||||
|
print(s)
|
||||||
|
|
||||||
|
|
||||||
|
CACHE = {}
|
||||||
|
|
||||||
|
|
||||||
|
def play(a, b):
|
||||||
|
if len(a) == 0 or len(b) == 0:
|
||||||
|
return a, b
|
||||||
|
|
||||||
|
s = (tuple(a), tuple(b))
|
||||||
|
if s in CACHE:
|
||||||
|
return CACHE[s]
|
||||||
|
|
||||||
|
seen = set()
|
||||||
|
while a and b:
|
||||||
|
s = (tuple(a), tuple(b))
|
||||||
|
if s in seen:
|
||||||
|
return True, False
|
||||||
|
seen.add(s)
|
||||||
|
|
||||||
|
x, y = a.popleft(), b.popleft()
|
||||||
|
if x <= len(a) and y <= len(b):
|
||||||
|
ar, br = play(deque(list(a)[:x]), deque(list(b)[:y]))
|
||||||
|
if ar:
|
||||||
|
a.append(x)
|
||||||
|
a.append(y)
|
||||||
|
elif br:
|
||||||
|
b.append(y)
|
||||||
|
b.append(x)
|
||||||
|
else:
|
||||||
|
assert False
|
||||||
|
else:
|
||||||
|
if x > y:
|
||||||
|
a.append(x)
|
||||||
|
a.append(y)
|
||||||
|
elif y > x:
|
||||||
|
b.append(y)
|
||||||
|
b.append(x)
|
||||||
|
else:
|
||||||
|
assert False
|
||||||
|
|
||||||
|
s = (tuple(a), tuple(b))
|
||||||
|
if s not in CACHE:
|
||||||
|
CACHE[s] = (a, b)
|
||||||
|
|
||||||
|
return a, b
|
||||||
|
|
||||||
|
|
||||||
|
a, b = data.strip().split("\n\n")
|
||||||
|
a = deque(ints(a)[1:])
|
||||||
|
b = deque(ints(b)[1:])
|
||||||
|
a, b = play(a, b)
|
||||||
|
|
||||||
|
for p in [a, b]:
|
||||||
|
s = 0
|
||||||
|
for i, c in enumerate(reversed(p)):
|
||||||
|
s += (i + 1) * c
|
||||||
|
if s > 0:
|
||||||
|
print(s)
|
||||||
73
2020/d23.py
Normal file
73
2020/d23.py
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
from lib import get_data
|
||||||
|
|
||||||
|
data = get_data(__file__)
|
||||||
|
|
||||||
|
cups = list(map(int, list(data.strip())))
|
||||||
|
min_label = min(cups)
|
||||||
|
max_label = max(cups)
|
||||||
|
|
||||||
|
cmap = {}
|
||||||
|
for i in range(len(cups)):
|
||||||
|
cmap[cups[i]] = cups[(i + 1) % len(cups)]
|
||||||
|
|
||||||
|
cc = cups[0]
|
||||||
|
for _ in range(100):
|
||||||
|
a = cmap[cc]
|
||||||
|
b = cmap[a]
|
||||||
|
c = cmap[b]
|
||||||
|
|
||||||
|
cmap[cc] = cmap[c]
|
||||||
|
|
||||||
|
dest = cc - 1
|
||||||
|
while dest in {a, b, c} or dest < min_label:
|
||||||
|
dest = dest - 1
|
||||||
|
if dest < min_label:
|
||||||
|
dest = max_label
|
||||||
|
|
||||||
|
cmap[c] = cmap[dest]
|
||||||
|
cmap[dest] = a
|
||||||
|
|
||||||
|
cc = cmap[cc]
|
||||||
|
|
||||||
|
s = ""
|
||||||
|
cup = cmap[1]
|
||||||
|
while cup != 1:
|
||||||
|
s += str(cup)
|
||||||
|
cup = cmap[cup]
|
||||||
|
print(s)
|
||||||
|
|
||||||
|
cups = list(map(int, list(data.strip())))
|
||||||
|
current = max(cups) + 1
|
||||||
|
while len(cups) != 1_000_000:
|
||||||
|
cups.append(current)
|
||||||
|
current += 1
|
||||||
|
|
||||||
|
min_label = min(cups)
|
||||||
|
max_label = max(cups)
|
||||||
|
cmap = {}
|
||||||
|
for i in range(len(cups)):
|
||||||
|
cmap[cups[i]] = cups[(i + 1) % len(cups)]
|
||||||
|
|
||||||
|
cc = cups[0]
|
||||||
|
for _ in range(10_000_000):
|
||||||
|
a = cmap[cc]
|
||||||
|
b = cmap[a]
|
||||||
|
c = cmap[b]
|
||||||
|
|
||||||
|
cmap[cc] = cmap[c]
|
||||||
|
|
||||||
|
dest = cc - 1
|
||||||
|
while dest in {a, b, c} or dest < min_label:
|
||||||
|
dest = dest - 1
|
||||||
|
if dest < min_label:
|
||||||
|
dest = max_label
|
||||||
|
|
||||||
|
cmap[c] = cmap[dest]
|
||||||
|
cmap[dest] = a
|
||||||
|
|
||||||
|
cc = cmap[cc]
|
||||||
|
|
||||||
|
a = cmap[1]
|
||||||
|
b = cmap[a]
|
||||||
|
|
||||||
|
print(a * b)
|
||||||
59
2020/d24.py
Normal file
59
2020/d24.py
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
from lib import get_data, add2
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
DIRS = {
|
||||||
|
"e": (0, 2),
|
||||||
|
"w": (0, -2),
|
||||||
|
"se": (1, 1),
|
||||||
|
"sw": (1, -1),
|
||||||
|
"nw": (-1, -1),
|
||||||
|
"ne": (-1, 1),
|
||||||
|
}
|
||||||
|
|
||||||
|
data = get_data(__file__)
|
||||||
|
|
||||||
|
tiles_black = set()
|
||||||
|
|
||||||
|
for row in data.splitlines():
|
||||||
|
row = row.strip()
|
||||||
|
i = 0
|
||||||
|
pos = (0, 0)
|
||||||
|
while i < len(row):
|
||||||
|
d = None
|
||||||
|
if row[i] in DIRS:
|
||||||
|
d = DIRS[row[i]]
|
||||||
|
i += 1
|
||||||
|
elif "".join(row[i : i + 2]) in DIRS:
|
||||||
|
d = DIRS["".join(row[i : i + 2])]
|
||||||
|
i += 2
|
||||||
|
else:
|
||||||
|
assert False
|
||||||
|
assert d is not None
|
||||||
|
pos = add2(pos, d)
|
||||||
|
if pos in tiles_black:
|
||||||
|
tiles_black.remove(pos)
|
||||||
|
else:
|
||||||
|
tiles_black.add(pos)
|
||||||
|
|
||||||
|
print(len(tiles_black))
|
||||||
|
|
||||||
|
for _ in range(100):
|
||||||
|
nbs = defaultdict(int)
|
||||||
|
for bt in tiles_black:
|
||||||
|
for d in DIRS.values():
|
||||||
|
nb = add2(bt, d)
|
||||||
|
nbs[nb] += 1
|
||||||
|
new_tiles_black = set()
|
||||||
|
for pos, count in nbs.items():
|
||||||
|
if pos in tiles_black:
|
||||||
|
if count == 0 or count > 2:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
new_tiles_black.add(pos)
|
||||||
|
else:
|
||||||
|
if count == 2:
|
||||||
|
new_tiles_black.add(pos)
|
||||||
|
|
||||||
|
tiles_black = new_tiles_black
|
||||||
|
|
||||||
|
print(len(tiles_black))
|
||||||
32
2020/d25.py
Normal file
32
2020/d25.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
from lib import get_data, ints
|
||||||
|
|
||||||
|
data = get_data(__file__)
|
||||||
|
a, b = ints(data)
|
||||||
|
|
||||||
|
v = 1
|
||||||
|
subject_number = 7
|
||||||
|
al, bl = None, None
|
||||||
|
|
||||||
|
for loop in range(100_000_000):
|
||||||
|
v *= subject_number
|
||||||
|
v %= 20201227
|
||||||
|
if al is None and v == a:
|
||||||
|
al = loop + 1
|
||||||
|
if bl is None and v == b:
|
||||||
|
bl = loop + 1
|
||||||
|
|
||||||
|
if al and bl:
|
||||||
|
break
|
||||||
|
|
||||||
|
assert al is not None
|
||||||
|
assert bl is not None
|
||||||
|
# print(al, bl)
|
||||||
|
|
||||||
|
v = 1
|
||||||
|
subject_number = b
|
||||||
|
for _ in range(al):
|
||||||
|
v *= subject_number
|
||||||
|
v %= 20201227
|
||||||
|
|
||||||
|
print(v)
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
from lib import get_data, Grid2D
|
from lib import get_data
|
||||||
|
|
||||||
required = [
|
required = [
|
||||||
"byr",
|
"byr",
|
||||||
@@ -7,7 +7,8 @@ required = [
|
|||||||
"hgt",
|
"hgt",
|
||||||
"hcl",
|
"hcl",
|
||||||
"ecl",
|
"ecl",
|
||||||
"pid",]
|
"pid",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
# hgt (Height) - a number followed by either cm or in:
|
# hgt (Height) - a number followed by either cm or in:
|
||||||
|
|||||||
40
2020/d5.py
Normal file
40
2020/d5.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
from lib import get_data
|
||||||
|
|
||||||
|
|
||||||
|
def part_1(data):
|
||||||
|
rs = []
|
||||||
|
for line in data.splitlines():
|
||||||
|
rl, ru = 0, 127
|
||||||
|
cl, cu = 0, 7
|
||||||
|
for c in line.strip():
|
||||||
|
rh = (ru - rl) // 2
|
||||||
|
ch = (cu - cl) // 2
|
||||||
|
if c == "B":
|
||||||
|
rl = rl + rh + 1
|
||||||
|
elif c == "F":
|
||||||
|
ru = rl + rh
|
||||||
|
elif c == "R":
|
||||||
|
cl = cl + ch + 1
|
||||||
|
elif c == "L":
|
||||||
|
cu = cl + ch
|
||||||
|
else:
|
||||||
|
assert False
|
||||||
|
assert rl == ru
|
||||||
|
assert cl == cu
|
||||||
|
r_new = rl * 8 + cl
|
||||||
|
rs.append(r_new)
|
||||||
|
|
||||||
|
print(max(rs))
|
||||||
|
rs = sorted(rs)
|
||||||
|
for i in range(len(rs) - 1):
|
||||||
|
if rs[i] + 1 != rs[i + 1]:
|
||||||
|
print(rs[i] + 1)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
data = get_data(__file__)
|
||||||
|
part_1(data)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
24
2020/d6.py
Normal file
24
2020/d6.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
from lib import get_data
|
||||||
|
|
||||||
|
|
||||||
|
def part_1(data):
|
||||||
|
r1, r2 = 0, 0
|
||||||
|
for group in data.split("\n\n"):
|
||||||
|
r1 += len(set(group.replace("\n", "")))
|
||||||
|
|
||||||
|
s = set(group.splitlines()[0])
|
||||||
|
for line in group.splitlines():
|
||||||
|
s &= set(line)
|
||||||
|
r2 += len(s)
|
||||||
|
|
||||||
|
print(r1)
|
||||||
|
print(r2)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
data = get_data(__file__)
|
||||||
|
part_1(data)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
47
2020/d7.py
Normal file
47
2020/d7.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
from lib import get_data
|
||||||
|
|
||||||
|
|
||||||
|
def part_1(data):
|
||||||
|
bags = {}
|
||||||
|
for line in data.splitlines():
|
||||||
|
left, right = line.split(" contain ")
|
||||||
|
lb = " ".join(left.split()[:2])
|
||||||
|
rbs = right.replace(" bag", "").replace(" bags", "").replace(".", "").split(",")
|
||||||
|
if "no others" in rbs:
|
||||||
|
rbs = []
|
||||||
|
else:
|
||||||
|
rbs = [[int(v.split()[0]), " ".join(v.split()[1:])] for v in rbs]
|
||||||
|
assert lb not in bags
|
||||||
|
bags[lb] = rbs
|
||||||
|
|
||||||
|
for lb, rbs in bags.items():
|
||||||
|
for t in rbs:
|
||||||
|
if t[0] > 1:
|
||||||
|
t[1] = t[1][:-1]
|
||||||
|
|
||||||
|
can_hold = set(["shiny gold"])
|
||||||
|
can_hold_count = -1
|
||||||
|
while can_hold_count != len(can_hold):
|
||||||
|
can_hold_count = len(can_hold)
|
||||||
|
for lb, rbs in bags.items():
|
||||||
|
for _, bag_color in rbs:
|
||||||
|
if bag_color in can_hold:
|
||||||
|
can_hold.add(lb)
|
||||||
|
print(len(can_hold) - 1)
|
||||||
|
|
||||||
|
def count(color):
|
||||||
|
r = 1 # the bag itself
|
||||||
|
for c, icolor in bags[color]:
|
||||||
|
r += c * count(icolor)
|
||||||
|
return r
|
||||||
|
|
||||||
|
print(count("shiny gold") - 1)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
data = get_data(__file__)
|
||||||
|
part_1(data)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
71
2020/d8.py
Normal file
71
2020/d8.py
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
from lib import get_data, str_to_ints
|
||||||
|
|
||||||
|
|
||||||
|
def part_1(data):
|
||||||
|
insts = data.splitlines()
|
||||||
|
acc = 0
|
||||||
|
i = 0
|
||||||
|
seen = set()
|
||||||
|
|
||||||
|
while i < len(insts) and i not in seen:
|
||||||
|
inst = insts[i]
|
||||||
|
seen.add(i)
|
||||||
|
if inst.startswith("acc"):
|
||||||
|
(v,) = str_to_ints(inst)
|
||||||
|
acc += v
|
||||||
|
i += 1
|
||||||
|
elif inst.startswith("jmp"):
|
||||||
|
(v,) = str_to_ints(inst)
|
||||||
|
i += v
|
||||||
|
elif inst.startswith("nop"):
|
||||||
|
i += 1
|
||||||
|
else:
|
||||||
|
assert False
|
||||||
|
|
||||||
|
print(acc)
|
||||||
|
|
||||||
|
for j in range(len(insts)):
|
||||||
|
|
||||||
|
if "jmp" in insts[j]:
|
||||||
|
insts[j] = insts[j].replace("jmp", "nop")
|
||||||
|
elif "nop" in insts[j]:
|
||||||
|
insts[j] = insts[j].replace("nop", "jmp")
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
acc = 0
|
||||||
|
seen = set()
|
||||||
|
while i < len(insts) and i not in seen:
|
||||||
|
inst = insts[i]
|
||||||
|
seen.add(i)
|
||||||
|
if inst.startswith("acc"):
|
||||||
|
(v,) = str_to_ints(inst)
|
||||||
|
acc += v
|
||||||
|
i += 1
|
||||||
|
elif inst.startswith("jmp"):
|
||||||
|
(v,) = str_to_ints(inst)
|
||||||
|
i += v
|
||||||
|
elif inst.startswith("nop"):
|
||||||
|
i += 1
|
||||||
|
else:
|
||||||
|
assert False
|
||||||
|
|
||||||
|
if not i in seen:
|
||||||
|
print(acc)
|
||||||
|
|
||||||
|
if "jmp" in insts[j]:
|
||||||
|
insts[j] = insts[j].replace("jmp", "nop")
|
||||||
|
elif "nop" in insts[j]:
|
||||||
|
insts[j] = insts[j].replace("nop", "jmp")
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
data = get_data(__file__)
|
||||||
|
part_1(data)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
33
2020/d9.py
Normal file
33
2020/d9.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
from lib import get_data
|
||||||
|
|
||||||
|
|
||||||
|
def part_1(data):
|
||||||
|
target = 0
|
||||||
|
xs = list(map(int, data.splitlines()))
|
||||||
|
for i in range(25, len(xs)):
|
||||||
|
x = xs[i]
|
||||||
|
good = False
|
||||||
|
for j in range(i - 25, i):
|
||||||
|
for k in range(j, i):
|
||||||
|
if xs[j] + xs[k] == x:
|
||||||
|
good = True
|
||||||
|
if not good:
|
||||||
|
print(x)
|
||||||
|
target = x
|
||||||
|
break
|
||||||
|
|
||||||
|
for i in range(len(xs)):
|
||||||
|
for j in range(i + 1, len(xs)):
|
||||||
|
if sum(xs[i : j + 1]) == target:
|
||||||
|
r = xs[i : j + 1]
|
||||||
|
print(min(r) + max(r))
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
data = get_data(__file__)
|
||||||
|
part_1(data)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
20
2021/d1.py
Normal file
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
|
||||||
161
2022/d19.py
161
2022/d19.py
@@ -1,117 +1,66 @@
|
|||||||
from lib import *
|
import re
|
||||||
|
from lib import get_data, str_to_ints
|
||||||
|
from math import ceil
|
||||||
|
|
||||||
EXAMPLE = """
|
data = get_data(__file__)
|
||||||
Blueprint 1:
|
|
||||||
Each ore robot costs 4 ore.
|
|
||||||
Each clay robot costs 2 ore.
|
|
||||||
Each obsidian robot costs 3 ore and 14 clay.
|
|
||||||
Each geode robot costs 2 ore and 7 obsidian.
|
|
||||||
|
|
||||||
Blueprint 2:
|
p1 = 0
|
||||||
Each ore robot costs 2 ore.
|
p2 = 1
|
||||||
Each clay robot costs 3 ore.
|
|
||||||
Each obsidian robot costs 3 ore and 8 clay.
|
|
||||||
Each geode robot costs 3 ore and 12 obsidian.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def solve(input: Input, second=False):
|
for line in data.splitlines():
|
||||||
if not second:
|
id = str_to_ints(line)[0]
|
||||||
res = 0
|
|
||||||
else:
|
|
||||||
res = 1
|
|
||||||
bps = []
|
bps = []
|
||||||
for line in input.text.replace(".", ".\n").replace(":", ":\n").splitlines():
|
maxmin = [0, 0, 0]
|
||||||
if "Blueprint" in line:
|
for part in line.split(". "):
|
||||||
bps.append([])
|
bp = []
|
||||||
elif "ore robot" in line:
|
for amount, mineral in re.findall(r"(\d+) (\w+)", part):
|
||||||
cost = str_to_ints(line)
|
amount = int(amount)
|
||||||
cost += [0, 0]
|
mi = ["ore", "clay", "obsidian", "geode"].index(mineral)
|
||||||
bps[-1].append(cost)
|
bp.append((amount, mi))
|
||||||
elif "clay robot" in line:
|
maxmin[mi] = max(maxmin[mi], amount)
|
||||||
cost = str_to_ints(line)
|
bps.append(bp)
|
||||||
cost += [0, 0]
|
|
||||||
bps[-1].append(cost)
|
|
||||||
elif "obsidian robot" in line:
|
|
||||||
cost = str_to_ints(line)
|
|
||||||
cost += [0,]
|
|
||||||
bps[-1].append(cost)
|
|
||||||
elif "geode robot" in line:
|
|
||||||
cost = str_to_ints(line)
|
|
||||||
cost.insert(1, 0)
|
|
||||||
bps[-1].append(cost)
|
|
||||||
|
|
||||||
if second:
|
def dfs(time, mins, bots, cache):
|
||||||
bps = bps[:3]
|
if time == 0:
|
||||||
time = 32
|
return mins[3]
|
||||||
|
|
||||||
|
key = (time, *mins, *bots)
|
||||||
|
if key in cache:
|
||||||
|
return cache[key]
|
||||||
|
|
||||||
|
maxv = mins[3] + time * bots[3]
|
||||||
|
for bi, recipe in enumerate(bps):
|
||||||
|
if bi != 3 and bots[bi] >= maxmin[bi]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
wait = 0
|
||||||
|
for ra, ri in recipe:
|
||||||
|
if bots[ri] == 0:
|
||||||
|
break
|
||||||
|
wait = max(ceil((ra - mins[ri]) / bots[ri]), wait)
|
||||||
else:
|
else:
|
||||||
time = 24
|
remtime = time - wait - 1
|
||||||
|
if remtime <= 0:
|
||||||
|
continue
|
||||||
|
|
||||||
end_states = []
|
bots_ = list(bots)
|
||||||
for i, bp in enumerate(bps):
|
mins_ = [m + b * (wait + 1) for m, b in zip(mins, bots)]
|
||||||
# ((ore bots, clay bots, obs bots, geo bots), (ore, clay, obs, geo))
|
for ra, ri in recipe:
|
||||||
start = ((1, 0, 0, 0), (0, 0, 0, 0))
|
mins_[ri] -= ra
|
||||||
states = [start]
|
bots_[bi] += 1
|
||||||
seen = set(states)
|
|
||||||
|
|
||||||
for _ in range(time):
|
for i in range(3):
|
||||||
new_states = []
|
mins_[i] = min(mins_[i], maxmin[i] * remtime)
|
||||||
while states:
|
v = dfs(remtime, tuple(mins_), tuple(bots_), cache)
|
||||||
bots, ress = states.pop()
|
maxv = max(v, maxv)
|
||||||
|
|
||||||
add_ress = [0, 0, 0, 0]
|
key = (time, *mins, *bots)
|
||||||
for boti, count in enumerate(bots):
|
cache[key] = maxv
|
||||||
add_ress[boti] += count
|
return maxv
|
||||||
|
|
||||||
all_built = True
|
p1 += id * dfs(24, (0, 0, 0, 0), (1, 0, 0, 0), {})
|
||||||
for boti, cost in enumerate(bp):
|
if id < 4:
|
||||||
if ress[0] >= cost[0] and ress[1] >= cost[1] and ress[2] >= cost[2]:
|
p2 *= dfs(32, (0, 0, 0, 0), (1, 0, 0, 0), {})
|
||||||
new_ress = list(ress)
|
|
||||||
new_ress[0] -= cost[0]
|
|
||||||
new_ress[1] -= cost[1]
|
|
||||||
new_ress[2] -= cost[2]
|
|
||||||
new_ress = tuple(map(sum, zip(new_ress, add_ress)))
|
|
||||||
new_bots = list(bots)
|
|
||||||
new_bots[boti] += 1
|
|
||||||
new_state = (tuple(new_bots), new_ress)
|
|
||||||
if not new_state in seen:
|
|
||||||
new_states.append(new_state)
|
|
||||||
seen.add(new_state)
|
|
||||||
else:
|
|
||||||
all_built = False
|
|
||||||
|
|
||||||
# XXX: our search space is too large here it is possible to
|
print(p1)
|
||||||
# optimze by not storing reduntant paths (paths where we acrue
|
print(p2)
|
||||||
# more of a resource than we need), but I don't know how to
|
|
||||||
# make it more efficient right now.
|
|
||||||
if not all_built:
|
|
||||||
new_ress = tuple(map(sum, zip(ress, add_ress)))
|
|
||||||
new_state = (bots, new_ress)
|
|
||||||
if not new_state in seen:
|
|
||||||
new_states.append(new_state)
|
|
||||||
seen.add(new_state)
|
|
||||||
|
|
||||||
# prune to keep search space "reasonable"
|
|
||||||
states = sorted(new_states, key=lambda s: list(reversed(s[0])), reverse=True)[:100000]
|
|
||||||
|
|
||||||
if not second:
|
|
||||||
r = max(states, key=lambda s: s[1][3])
|
|
||||||
q = (i + 1) * r[1][3]
|
|
||||||
# print(i + 1, r, q)
|
|
||||||
res += q
|
|
||||||
else:
|
|
||||||
r = max(states, key=lambda r: r[1][3])
|
|
||||||
res *= r[1][3]
|
|
||||||
return res
|
|
||||||
|
|
||||||
def main():
|
|
||||||
DAY_INPUT = "d19.txt"
|
|
||||||
e1 = solve(Input(EXAMPLE))
|
|
||||||
print("Example 1:", e1)
|
|
||||||
assert e1 == 33
|
|
||||||
print("Solution 1:", solve(Input(DAY_INPUT)))
|
|
||||||
print("Example 2:", solve(Input(EXAMPLE), True))
|
|
||||||
print("Solution 2:", solve(Input(DAY_INPUT), True))
|
|
||||||
return
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
|
|||||||
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))
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user