diff --git a/2021/d19.py b/2021/d19.py new file mode 100644 index 0000000..bc3820b --- /dev/null +++ b/2021/d19.py @@ -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) diff --git a/README.md b/README.md index be2270a..95cdd5c 100644 --- a/README.md +++ b/README.md @@ -190,7 +190,7 @@ Solutions and utility script for Advent of Code challenges in Python. - Day 16: 50:01 (Way too slow. Was non-trivial but fun. Much better was feasible.) - Day 17: 21:59 (Not tricky again but struggling for no reason.) - Day 18: 162:00 (I couldn't figure out how to solve it as a tree so I did the basic way.) -- Day 19: +- Day 19: days (Super hard for me but super fun ultimately once I had the right approach.) - Day 20: 105:00 (That wasn't easy but was able to solve in one go.) - Day 21: 37:45 (Wasn't hard but I was just too slow.) - Day 22: 142:00 (Wonderful problem and hard for me but learned something new for sure.)