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