@@ -1 +1 @@ | |||
🐄 | |||
🐰 |
@@ -4,6 +4,7 @@ export | |||
.SILENT: | |||
FILE := $(shell find . -path "./y????/p??.*" -type f | xargs ls -rt | tail -n 1) | |||
PYTHON := docker compose run --rm advent python | |||
YEAR := $(shell echo ${FILE} | sed -E 's/.+y([0-9]+).+/\1/') | |||
DAY := $(shell echo ${FILE} | sed -E 's/.+p([0-9]+).+/\1/' | bc) | |||
@@ -14,20 +15,20 @@ CODE := $(BASE).py | |||
DATA := $(BASE).dat | |||
TEST := $(BASE).dtt | |||
main: ${TEST} | |||
echo 'test': | |||
cat ${TEST} | docker compose run --rm advent python -u ${CODE} | |||
echo 'test:' | |||
cat ${TEST} | ${PYTHON} -u ${CODE} | |||
clean: ${DATA} | |||
echo 'real:' | |||
cat ${DATA} | docker compose run --rm advent python -u ${CODE} | |||
cat ${DATA} | ${PYTHON} -u ${CODE} | |||
save: | |||
git add . | |||
test `git log -1 --format=%s` == `cat VERSION` \ | |||
&& git commit --amend --reuse-message=head \ | |||
|| git commit -m `cat VERSION` | |||
git push -f | |||
${DATA} ${TEST}: | |||
# avoid spam in the lead up to the event |
@@ -0,0 +1,69 @@ | |||
import collections | |||
import itertools | |||
import re | |||
def part1(H): | |||
""" | |||
2 variables, 2 equations | |||
px1 + vx1 * t1 == px2 + vx2 * t2 | |||
py1 + vy1 * t1 == py2 + vy2 * t2 | |||
t1 = (px2 - px1) / vx1 + (vx2 / vx1) * t2 | |||
t1 = (py2 - py1) / vy1 + (vy2 / vy1) * t2 | |||
(px2 - px1) / vx1 - (py2 - py1) / vy1 = (vy2 / vy1 - vx2 / vx1) * t2 | |||
t2 = c1 / c2 | |||
c1 = (px2 - px1) / vx1 - (py2 - py1) / vy1 | |||
c2 = (vy2 / vy1 - vx2 / vx1) | |||
""" | |||
n = 0 | |||
lo, hi = (7, 27) if len(H) < 100 else (2e14, 4e14) | |||
for aa, bb in itertools.combinations(H, 2): | |||
px1, py1, pz1, vx1, vy1, vz1 = aa | |||
px2, py2, pz2, vx2, vy2, vz2 = bb | |||
c1 = (px2 - px1) / vx1 - (py2 - py1) / vy1 | |||
c2 = (vy2 / vy1 - vx2 / vx1) | |||
if c2 == 0: continue | |||
t2 = (c1 / c2) | |||
t1 = (px2 - px1) / vx1 + (vx2 / vx1) * t2 | |||
if t1 <= 0 or t2 <= 0: continue | |||
if not lo < px1 + vx1 * t1 < hi: continue | |||
if not lo < py1 + vy1 * t1 < hi: continue | |||
if not lo < px2 + vx2 * t2 < hi: continue | |||
if not lo < py2 + vy2 * t2 < hi: continue | |||
n += 1 | |||
return n | |||
def part2(H): | |||
""" | |||
6 + t variables, 3 * t equations | |||
""" | |||
try: | |||
import z3 | |||
except: | |||
return 'Need z3-solver' | |||
Q = {k: z3.Real(k) for k in 'px py pz vx vy vz t0 t1 t2'.split()} | |||
solver = z3.Solver() | |||
solver.add(H[0].px + H[0].vx * Q['t0'] == Q['px'] + Q['vx'] * Q['t0']) | |||
solver.add(H[0].py + H[0].vy * Q['t0'] == Q['py'] + Q['vy'] * Q['t0']) | |||
solver.add(H[0].pz + H[0].vz * Q['t0'] == Q['pz'] + Q['vz'] * Q['t0']) | |||
solver.add(H[1].px + H[1].vx * Q['t1'] == Q['px'] + Q['vx'] * Q['t1']) | |||
solver.add(H[1].py + H[1].vy * Q['t1'] == Q['py'] + Q['vy'] * Q['t1']) | |||
solver.add(H[1].pz + H[1].vz * Q['t1'] == Q['pz'] + Q['vz'] * Q['t1']) | |||
solver.add(H[2].px + H[2].vx * Q['t2'] == Q['px'] + Q['vx'] * Q['t2']) | |||
solver.add(H[2].py + H[2].vy * Q['t2'] == Q['py'] + Q['vy'] * Q['t2']) | |||
solver.add(H[2].pz + H[2].vz * Q['t2'] == Q['pz'] + Q['vz'] * Q['t2']) | |||
solver.check() | |||
model = solver.model() | |||
return model.eval(Q['px'] + Q['py'] + Q['pz']) | |||
text = open(0).read() | |||
Hail = collections.namedtuple('Hail', 'px py pz vx vy vz') | |||
H = [Hail(*[int(n) for n in re.findall(r'-?\d+', line)]) for line in text.splitlines()] | |||
print(part1(H)) | |||
print(part2(H)) |
@@ -0,0 +1,66 @@ | |||
import random | |||
import itertools | |||
import copy | |||
import collections | |||
def make_graph(text): | |||
graph = collections.defaultdict(set) | |||
for aa, *bbs in map(str.split, text.replace(':', '').splitlines()): | |||
graph[aa] |= set(bbs) | |||
for bb in bbs: | |||
graph[bb] |= {aa} | |||
return graph | |||
def cut(graph, wires): | |||
graph = graph.copy() | |||
for aa, bb in wires: | |||
graph[aa] = graph[aa] - {bb} | |||
graph[bb] = graph[bb] - {aa} | |||
return graph | |||
def subgraphs(graph): | |||
conns = [] | |||
while graph: | |||
state = {next(iter(graph))} | |||
seen = set() | |||
while state: | |||
state = {new for old in state for new in graph[old] if new not in seen} | |||
seen |= state | |||
conns.append(seen) | |||
for node in seen: | |||
graph.pop(node) | |||
return conns | |||
def shortest_path(graph, aa, bb): | |||
state = {aa: None} | |||
seen = {} | |||
while bb not in state: | |||
state = {new: old for old in state for new in graph[old] if new not in seen} | |||
seen |= state | |||
path = [bb] | |||
while path[-1] != aa: | |||
path.append(seen[path[-1]]) | |||
return path | |||
def rank_wires(graph, n_cycles=100, n_top=20): | |||
tally = collections.Counter() | |||
for aa, bb in sorted(itertools.combinations(graph, 2))[:n_cycles]: | |||
path = shortest_path(graph, aa, bb) | |||
tally.update(frozenset(pair) for pair in zip(path, path[1:])) | |||
return [k for k, _ in tally.most_common(n_top)] | |||
text = open(0).read() | |||
graph = make_graph(text) | |||
for triplet in itertools.combinations(rank_wires(graph), 3): | |||
mod_graph = cut(graph, triplet) | |||
subs = subgraphs(mod_graph) | |||
if len(subs) == 2: | |||
print(len(subs[0]) * len(subs[1])) | |||
break |