|
| 1 | +#!/bin/python |
| 2 | +"""Advent of Code, Day 17: Chronospatial Computer.""" |
| 3 | +from __future__ import annotations |
| 4 | + |
| 5 | +import collections |
| 6 | +import functools |
| 7 | +import itertools |
| 8 | +import math |
| 9 | +import random |
| 10 | +import re |
| 11 | + |
| 12 | +from lib import aoc |
| 13 | + |
| 14 | +SAMPLE = [ |
| 15 | + """\ |
| 16 | +Register A: 729 |
| 17 | +Register B: 0 |
| 18 | +Register C: 0 |
| 19 | +
|
| 20 | +Program: 0,1,5,4,3,0""", # 76 |
| 21 | + """\ |
| 22 | +Register A: 2024 |
| 23 | +Register B: 0 |
| 24 | +Register C: 0 |
| 25 | +
|
| 26 | +Program: 0,3,5,4,3,0""" |
| 27 | +] |
| 28 | + |
| 29 | +LineType = int |
| 30 | +InputType = list[LineType] |
| 31 | + |
| 32 | + |
| 33 | +class Day17(aoc.Challenge): |
| 34 | + """Day 17: Chronospatial Computer.""" |
| 35 | + |
| 36 | + DEBUG = True |
| 37 | + # Default is True. On live solve, submit one tests pass. |
| 38 | + # SUBMIT = {1: False, 2: False} |
| 39 | + |
| 40 | + TESTS = [ |
| 41 | + aoc.TestCase(part=1, inputs=SAMPLE[0], want="4,6,3,5,6,3,5,2,1,0"), |
| 42 | + # aoc.TestCase(part=2, inputs=SAMPLE[1], want=117440), |
| 43 | + aoc.TestCase(part=2, inputs=SAMPLE[0], want=aoc.TEST_SKIP), |
| 44 | + ] |
| 45 | + |
| 46 | + INPUT_PARSER = aoc.ParseBlocks([aoc.parse_ints, aoc.parse_re_findall_int(r"\d+")]) |
| 47 | + |
| 48 | + def simulate(self, reg, instructions) -> list[int]: |
| 49 | + ptr = 0 |
| 50 | + |
| 51 | + def val(i, combo): |
| 52 | + if not combo: |
| 53 | + return i |
| 54 | + if 0 <= i <= 3: |
| 55 | + return i |
| 56 | + return reg[{4: "a", 5: "b", 6: "c"}[i]] |
| 57 | + |
| 58 | + out = [] |
| 59 | + # print(ptr, len(instructions)) |
| 60 | + while ptr < len(instructions) - 1: |
| 61 | + instruction, op = instructions[ptr], instructions[ptr + 1] |
| 62 | + ptr += 2 |
| 63 | + # print("a", "_".join("".join(i) for i in itertools.batched(bin(reg["a"])[2:], n=3))) |
| 64 | + |
| 65 | + if instruction == 0: # adv |
| 66 | + reg["a"] = reg["a"] // (2 ** val(op, True)) |
| 67 | + if instruction == 1: # bxl |
| 68 | + reg["b"] = reg["b"] ^ op |
| 69 | + if instruction == 2: # bst |
| 70 | + reg["b"] = val(op, True) % 8 |
| 71 | + if instruction == 3: # jnz |
| 72 | + if reg["a"] != 0: |
| 73 | + ptr = op |
| 74 | + if instruction == 4: # bxc |
| 75 | + reg["b"] = reg["b"] ^ reg["c"] |
| 76 | + if instruction == 5: # out |
| 77 | + # print(f"{ptr=}, {op=}, {val(op, True)}") |
| 78 | + # print("a", "_".join("".join(i) for i in itertools.batched(bin(reg["a"])[2:], n=3))) |
| 79 | + # print("Output", (val(op, True) % 8)) |
| 80 | + out.append((val(op, True) % 8)) |
| 81 | + if instruction == 6: # bdv |
| 82 | + reg["b"] = reg["a"] // (2 ** val(op, True)) |
| 83 | + if instruction == 7: # cdv |
| 84 | + reg["c"] = reg["a"] // (2 ** val(op, True)) |
| 85 | + # print(f"cdv dist = {reg['b']}, val = {reg['c'] % 8=}, inverted = {7 - (reg['c'] % 8)}",) |
| 86 | + # print(instruction, op) |
| 87 | + # for i in "abc": |
| 88 | + # print(i, reg[i] % 8, bin(reg[i] % 8)) |
| 89 | + |
| 90 | + return out |
| 91 | + |
| 92 | + def part1(self, puzzle_input: InputType) -> int: |
| 93 | + reg = {char: puzzle_input[0][i][0] for i, char in enumerate("abc")} |
| 94 | + instructions = puzzle_input[1][0] |
| 95 | + return ",".join(str(i) for i in self.simulate(reg, instructions)) |
| 96 | + |
| 97 | + |
| 98 | + def part2(self, puzzle_input: InputType) -> int: |
| 99 | + |
| 100 | + instructions = puzzle_input[1][0] |
| 101 | + |
| 102 | + def simulate(reg_a: int) -> list[int]: |
| 103 | + out = [] |
| 104 | + while reg_a: |
| 105 | + shift = reg_a & 7 ^ 7 |
| 106 | + digit = (3 ^ reg_a ^ (reg_a >> shift)) & 7 |
| 107 | + out.append(digit) |
| 108 | + reg_a = reg_a >> 3 |
| 109 | + return out |
| 110 | + |
| 111 | + test_val = 0b111_111_010_101 |
| 112 | + assert self.simulate({"a": test_val, "b": 0, "c": 0}, instructions) == [3, 6, 3, 3] |
| 113 | + assert simulate(test_val) == [3, 6, 3, 3] |
| 114 | + |
| 115 | + if True: |
| 116 | + mask = (1 << 13) - 8 |
| 117 | + def solve(given, digits): |
| 118 | + # print(f"solve({given=}, {digits=})") |
| 119 | + given <<= 3 |
| 120 | + options = set() |
| 121 | + for i, shift in itertools.product(range(8), repeat=2): |
| 122 | + shifted = (i << shift) & mask |
| 123 | + last_3 = shift ^ 7 |
| 124 | + # digit = 3 ^ last_3 ^ (shifted >> shift) |
| 125 | + # if digit != instructions[-digits]: |
| 126 | + # continue |
| 127 | + new_num = given | shifted | last_3 |
| 128 | + got = simulate(new_num) |
| 129 | + # print(got) |
| 130 | + if len(got) == digits and got == instructions[-len(got):]: |
| 131 | + options.add(new_num) |
| 132 | + if digits != 16: |
| 133 | + new_opts = set() |
| 134 | + for option in options: |
| 135 | + if (got := solve(option, digits + 1)) is not None: |
| 136 | + new_opts.add(got) |
| 137 | + options = new_opts |
| 138 | + if digits == 16: |
| 139 | + for option in options: |
| 140 | + if (got := simulate(option)) == instructions: |
| 141 | + print(f"{option} yields {got}") |
| 142 | + return min(options) if options else None |
| 143 | + |
| 144 | + return solve(0, 1) |
| 145 | + |
| 146 | + |
| 147 | + |
| 148 | + |
| 149 | + |
| 150 | + if False: |
| 151 | + for a in [0b111_111_010_101]: |
| 152 | + print("Test", a) |
| 153 | + print("Sim out", "".join(str(i) for i in self.simulate({"a": a, "b": 0, "c": 0}, instructions))) |
| 154 | + print("Run") |
| 155 | + print("a", "_".join("".join(i) for i in itertools.batched(bin(a)[2:], n=3))) |
| 156 | + out = "" |
| 157 | + while a: |
| 158 | + print("a", "_".join("".join(i) for i in itertools.batched(bin(a)[2:], n=3))) |
| 159 | + shift = a & 7 ^ 7 |
| 160 | + # print("shift", b) |
| 161 | + digit = (3 ^ a ^ (a >> shift)) & 7 |
| 162 | + out += str(digit) |
| 163 | + a = a >> 3 |
| 164 | + print("Simple out", out) |
| 165 | + return |
| 166 | + |
| 167 | + if False: |
| 168 | + want = instructions[:] |
| 169 | + want_str = "".join(str(i) for i in want) |
| 170 | + shifts = itertools.product(range(8), repeat=len(want)) |
| 171 | + protected = 0 |
| 172 | + for s in shifts: |
| 173 | + num = 0 |
| 174 | + for digit, a_part in zip(reversed(want), s): |
| 175 | + num <<= 3 |
| 176 | + protected <<= 3 |
| 177 | + do_not_touch = num & protected |
| 178 | + |
| 179 | + shift = a_part ^ 7 |
| 180 | + shift_part = digit ^ 3 ^ a |
| 181 | + # shift_part = (shift_part << shift) ^ |
| 182 | + |
| 183 | + |
| 184 | + |
| 185 | + |
| 186 | + neg_dist = dist ^ 7 |
| 187 | + num |= neg_dist | (digit ^ 7) << dist |
| 188 | + if num & 7 != neg_dist: |
| 189 | + break |
| 190 | + if num & (7 << dist) != (digit ^ 7): |
| 191 | + break |
| 192 | + if do_not_touch != num & protected: |
| 193 | + break |
| 194 | + protected |= 7 | 7 << dist |
| 195 | + else: |
| 196 | + print(num) |
| 197 | + a = num |
| 198 | + out = "" |
| 199 | + while a: |
| 200 | + bits = a & 7 |
| 201 | + b = bits ^ 7 |
| 202 | + out += str(7 ^ ((a >> b) & 7)) |
| 203 | + a = a >> 3 |
| 204 | + if out == want_str: |
| 205 | + assert num < 266582694363549 |
| 206 | + return num |
| 207 | + |
| 208 | + return |
| 209 | + |
| 210 | + |
| 211 | + else: |
| 212 | + for i in itertools.count(): |
| 213 | + if not i % 1000000: |
| 214 | + print(i) |
| 215 | + a = i |
| 216 | + outcount = 0 |
| 217 | + while a: |
| 218 | + b = (a % 8) ^ 7 |
| 219 | + c = a >> b |
| 220 | + b ^= c |
| 221 | + b ^= a |
| 222 | + out = b % 8 |
| 223 | + if out == want[outcount]: |
| 224 | + outcount += 1 |
| 225 | + if outcount == len_want: |
| 226 | + return i |
| 227 | + else: |
| 228 | + break |
| 229 | + a //= 8 |
| 230 | + |
| 231 | + reg = {char: puzzle_input[0][i][0] for i, char in enumerate("abc")} |
| 232 | + instructions = puzzle_input[1][0] |
| 233 | + want = [str(i) for i in instructions] |
| 234 | + |
| 235 | + for i in itertools.count(): |
| 236 | + reg["a"] = i |
| 237 | + got = self.simulate(reg.copy(), instructions) |
| 238 | + if got == instructions: |
| 239 | + return i |
| 240 | + if got[:-4] == [0,3,3,0]: |
| 241 | + print(i, got) |
| 242 | + |
| 243 | +# vim:expandtab:sw=4:ts=4 |
0 commit comments