diff --git a/day24.py b/day24.py new file mode 100644 index 0000000..5eb3341 --- /dev/null +++ b/day24.py @@ -0,0 +1,259 @@ +from __future__ import annotations + +import itertools + +from tools.aoc import AOCDay +from typing import Any + +OPS = { + "AND": lambda a, b: a & b, + "OR": lambda a, b: a | b, + "XOR": lambda a, b: a ^ b, +} + + +def padded_bin(number: int, pad: int = 45): + b = bin(number)[2:] + return "0" * (pad - len(b)) + b + + +class Gate: + def __init__(self, input_1: Wire, input_2: Wire, op: str, out: Wire = None) -> None: + self.input_1 = input_1 + self.input_2 = input_2 + self.op_name = op + self.op = OPS[op] + self.out = out + + def get_state(self) -> int: + return self.op(self.input_1.get_state(), self.input_2.get_state()) + + +class Wire: + def __init__(self, name: str, gate: Gate | None = None, state: int = 0) -> None: + self.name = name + self.gate = gate + self.state = state + + def get_state(self) -> int: + if self.gate is not None: + self.state = self.gate.get_state() + return self.state + + def print(self, depth: int = 0) -> None: + pad = " " * depth + if self.gate is not None: + print(f"{pad} {self.name} <- {self.gate.input_1.name} {self.gate.op_name} {self.gate.input_2.name}") + self.gate.input_1.print(depth + 2) + self.gate.input_2.print(depth + 2) + + +class Wires(list): + def __init__(self): + super().__init__() + self.wires = {} + self.lines = {"x": [], "y": [], "z": []} + self.wire_count = {"x": 0, "y": 0, "z": 0} + + def get_number(self, line: str = "z") -> int: + return int("".join(str(x.get_state()) for x in self.lines[line][::-1]), 2) + + def set_number(self, line: str, number: int) -> None: + wires = self.lines[line][::-1] + for i, c in enumerate(padded_bin(number, len(wires))): + wires[i].state = int(c) + + def swap_gates(self, wire_1: Wire | str, wire_2: Wire | str) -> None: + if isinstance(wire_1, str): + wire_1 = self.wires[wire_1] + if isinstance(wire_2, str): + wire_2 = self.wires[wire_2] + + wire_1.gate, wire_2.gate = wire_2.gate, wire_1.gate + + def get_bit_diff(self, number: int) -> list[int]: + my_z = self.get_number() + if my_z == number: + return [] + number_bin = padded_bin(number, self.wire_count["z"]) + my_bin_z = padded_bin(my_z, self.wire_count["z"]) + return [i for i, c in enumerate(number_bin) if c != my_bin_z[i]] + + def append(self, wire: Wire) -> None: + self.wires[wire.name] = wire + if wire.name[0] in self.wire_count: + self.wire_count[wire.name[0]] += 1 + if wire.name[0] in self.lines: + self.lines[wire.name[0]] = list(sorted(self.lines[wire.name[0]] + [wire], key=lambda w: w.name)) + super().append(wire) + + def __getitem__(self, item: int | str) -> Wire: + if isinstance(item, int): + return super()[item] + else: + return self.wires[item] + + def __contains__(self, item: Wire | str) -> bool: + if isinstance(item, str): + return item in self.wires + else: + return super().__contains__(item) + + +class Day(AOCDay): + inputs = [ + [ + (4, "input24_test"), + (2024, "input24_test2"), + (42883464055378, "input24"), + ], + [ + (None, "input24"), + ], + ] + + def parse_input(self) -> tuple[Wires, list[Gate]]: + init_state, init_gates = self.getMultiLineInputAsArray() + + wires = Wires() + for wire in init_state: + wire, wire_state = wire.split(": ") + wires.append(Wire(name=wire, state=int(wire_state))) + + gates = [] + done = set() + while len(gates) < len(init_gates): + for gate in init_gates: + if gate in done: + continue + input_1, op, input_2, _, output = gate.split() + if input_1 not in wires or input_2 not in wires: + continue + done.add(gate) + + gate = Gate(wires[input_1], wires[input_2], op) + gates.append(gate) + wires.append(Wire(name=output, gate=gate)) + gate.out = wires[output] + + return wires, gates + + def part1(self) -> Any: + return self.parse_input()[0].get_number() + + def part2(self) -> Any: + wires, gates = self.parse_input() + + # deduced by manually debugging the output from below + # need to find some automated solution in the future + wires.swap_gates("dtk", "vgs") + wires.swap_gates("z39", "pfw") + wires.swap_gates("z21", "shh") + wires.swap_gates("z33", "dqr") + wires["z21"].print() + + fishy_wires = [] + for w in wires: + if w.gate is None: + continue + if w.name.startswith("z") and w.gate.op_name != "XOR": + fishy_wires.append(w) + if w.gate.op_name == "OR": + if w.gate.input_1.gate is None or w.gate.input_1.gate.op_name != "AND": + fishy_wires.append(w) + if w.gate.input_2.gate is None or w.gate.input_2.gate.op_name != "AND": + fishy_wires.append(w) + else: + if w.gate.input_1.gate is not None and w.gate.input_1.gate.op_name not in ["XOR", "OR"]: + fishy_wires.append(w) + if w.gate.input_2.gate is not None and w.gate.input_2.gate.op_name not in ["XOR", "OR"]: + fishy_wires.append(w) + + for w in fishy_wires: + print( + f"{w.name} -> Input Gate: {w.gate.op_name} from {w.gate.input_1.name} ({w.gate.input_1.gate.op_name if w.gate.input_1.gate is not None else 'None'}) and {w.gate.input_2.name} ({w.gate.input_2.gate.op_name if w.gate.input_2.gate is not None else 'None'})" + ) + + # z21 -> Input Gate: AND from jsq (XOR) and vcj (OR) + # z33 -> Input Gate: OR from mtw (AND) and fdv (AND) + # z39 -> Input Gate: AND from x39 (None) and y39 (None) + # sjk -> Input Gate: OR from shh (XOR) and kcq (AND) + # z26 -> Input Gate: XOR from skh (OR) and vgs (AND) + # bmg -> Input Gate: AND from vgs (AND) and skh (OR) + # qcr -> Input Gate: OR from bmg (AND) and dtk (XOR) + # jqk -> Input Gate: OR from pfw (XOR) and kdd (AND) + + return "" + + old_x = wires.get_number("x") + old_y = wires.get_number("y") + old_real_z = wires.get_number("x") + wires.get_number("y") + + wires.set_number("x", int("101010101010101010101010101010101010101010101", 2)) + wires.set_number("y", int("010101010101010101010101010101010101010101010", 2)) + real_z = wires.get_number("x") + wires.get_number("y") + + # z-lines have to finish on an XOR gate, except for the most significant bit, which should have an OR gate + wrong_gates = [] + for i in range(46): + z_0 = [w for w in wires if w.name == "z%02d" % i][0] + if i < 45 and z_0.gate.op_name != "XOR": + wrong_gates.append(z_0) + elif i == 45 and z_0.gate.op_name != "OR": + wrong_gates.append(z_0) + + xor_gates = [xor.out for xor in gates if xor.op_name == "XOR" and not xor.out.name.startswith("z")] + + best = 10e9 + best_pairs = None + for comp in itertools.combinations(xor_gates, len(wrong_gates)): + for mesh in [list(zip(x, wrong_gates)) for x in itertools.permutations(comp, len(wrong_gates))]: + for wire_1, wire_2 in mesh: + wires.swap_gates(wire_1, wire_2) + try: + bit_rot = len(wires.get_bit_diff(real_z)) + except RecursionError: + bit_rot = 10e9 + if bit_rot < best: + best = bit_rot + best_pairs = mesh + for wire_1, wire_2 in mesh: + wires.swap_gates(wire_1, wire_2) + + print(best) + swapped = [] + for wire_1, wire_2 in best_pairs: + wires.swap_gates(wire_1, wire_2) + swapped.append(wire_1) + swapped.append(wire_2) + + wrong_wire = wires["z%02d" % (wires.wire_count["z"] - 1 - wires.get_bit_diff(real_z)[0])] + swapped.append(wrong_wire) + for x_wire in xor_gates: + if x_wire in swapped: + continue + wires.swap_gates(x_wire, wrong_wire) + try: + if len(wires.get_bit_diff(real_z)) == 0: + swapped.append(x_wire) + break + except RecursionError: + pass + wires.swap_gates(x_wire, wrong_wire) + + print(real_z) + print(wires.get_number()) + + wires.set_number("x", old_x) + wires.set_number("y", old_y) + print(old_real_z) + print(wires.get_number()) + + print(",".join(sorted(w.name for w in swapped))) + + return "" + + +if __name__ == "__main__": + day = Day(2024, 24) + day.run(verbose=True) diff --git a/inputs/input24 b/inputs/input24 new file mode 100644 index 0000000..39a50a4 --- /dev/null +++ b/inputs/input24 @@ -0,0 +1,313 @@ +x00: 1 +x01: 0 +x02: 0 +x03: 1 +x04: 1 +x05: 0 +x06: 0 +x07: 0 +x08: 0 +x09: 0 +x10: 0 +x11: 1 +x12: 0 +x13: 1 +x14: 1 +x15: 0 +x16: 1 +x17: 1 +x18: 1 +x19: 1 +x20: 0 +x21: 0 +x22: 1 +x23: 0 +x24: 1 +x25: 1 +x26: 1 +x27: 1 +x28: 1 +x29: 0 +x30: 0 +x31: 1 +x32: 0 +x33: 0 +x34: 1 +x35: 1 +x36: 0 +x37: 0 +x38: 0 +x39: 0 +x40: 0 +x41: 0 +x42: 1 +x43: 0 +x44: 1 +y00: 1 +y01: 0 +y02: 0 +y03: 1 +y04: 1 +y05: 1 +y06: 0 +y07: 0 +y08: 0 +y09: 1 +y10: 0 +y11: 0 +y12: 0 +y13: 1 +y14: 1 +y15: 1 +y16: 0 +y17: 0 +y18: 1 +y19: 0 +y20: 1 +y21: 1 +y22: 1 +y23: 0 +y24: 0 +y25: 1 +y26: 0 +y27: 0 +y28: 1 +y29: 1 +y30: 1 +y31: 1 +y32: 1 +y33: 0 +y34: 0 +y35: 0 +y36: 1 +y37: 1 +y38: 1 +y39: 0 +y40: 0 +y41: 1 +y42: 0 +y43: 0 +y44: 1 + +mvq XOR chf -> z05 +chf AND mvq -> pnn +nqp OR gqt -> ddt +pnn OR ntm -> ttr +kvq XOR ttr -> z06 +jqk XOR vnc -> z40 +kjf OR ftk -> sqn +mwb XOR sbr -> z09 +x42 XOR y42 -> vvg +x09 XOR y09 -> mwb +x19 AND y19 -> scf +x16 AND y16 -> gqt +pgd AND wjd -> dqf +fjc OR wdj -> jnh +x37 XOR y37 -> fpb +dqr XOR wnt -> z34 +x11 XOR y11 -> thj +shh OR kcq -> sjk +jwp OR dvc -> qhc +sjr OR jrk -> tfq +x20 AND y20 -> ssw +jqp AND vkp -> fdv +thj AND fgd -> ngq +ccb OR kfg -> tqd +x07 XOR y07 -> dgg +nmt OR jrg -> sjq +y40 AND x40 -> tcf +x02 AND y02 -> rgb +x15 AND y15 -> chh +gqn XOR sjq -> pfw +bhc AND fqq -> jvk +hsk OR kwq -> kdv +chh OR mqf -> pht +x00 AND y00 -> mcg +hhp AND hhg -> kbm +cph OR qjc -> nff +jnh XOR bdw -> z28 +y06 AND x06 -> qjc +y30 AND x30 -> kfg +y21 AND x21 -> kcq +vgs AND skh -> bmg +qgg AND bmw -> jwp +chn XOR jjk -> z25 +y34 XOR x34 -> wnt +sjk XOR dpc -> z22 +y11 AND x11 -> hqj +kbg XOR tcv -> z30 +pww AND pvw -> mvv +hqj OR ngq -> qgg +x23 AND y23 -> cpq +jnh AND bdw -> qhb +y04 AND x04 -> ggg +x25 AND y25 -> thm +x34 AND y34 -> fbd +pqk XOR srh -> z02 +y43 AND x43 -> gvv +kkk OR rgb -> gkk +jvk OR drq -> smj +pww XOR pvw -> z24 +tcv AND kbg -> ccb +wtw OR scf -> vcp +x03 AND y03 -> ngs +dqr AND wnt -> cgq +tsf AND kpk -> tjr +y18 AND x18 -> hsk +y26 XOR x26 -> dtk +y44 XOR x44 -> hhg +x10 XOR y10 -> wjp +kvq AND ttr -> cph +y35 XOR x35 -> bhc +wsw OR dqf -> pvj +jsq XOR vcj -> shh +pdf OR npr -> vkp +y12 AND x12 -> dvc +fsg XOR fdh -> z14 +dwf XOR rbf -> z32 +y24 XOR x24 -> pww +sqn AND fmd -> mqf +x35 AND y35 -> drq +wjp AND dvh -> bdf +smj AND tww -> ddk +hrm AND jhc -> kwq +jvv AND jww -> kff +pvt AND gkk -> pwn +hhg XOR hhp -> z44 +kdv XOR ntj -> z19 +y23 XOR x23 -> jww +sdg OR trv -> dvh +fdh AND fsg -> ftk +x01 XOR y01 -> vcn +y29 AND x29 -> ghw +hsg XOR dct -> z08 +kff OR cpq -> pvw +ddt AND nhg -> vft +ggv OR thm -> skh +mcg XOR vcn -> z01 +y41 AND x41 -> wsw +x14 XOR y14 -> fsg +y13 AND x13 -> rgp +rgp OR crm -> fdh +srh AND pqk -> kkk +tcf OR cpp -> pgd +y03 XOR x03 -> pvt +x33 AND y33 -> mtw +dgg AND nff -> gqk +y30 XOR x30 -> kbg +y07 AND x07 -> dvs +x44 AND y44 -> spc +y38 AND x38 -> nmt +hkv AND tqd -> ckb +y31 XOR x31 -> hkv +y02 XOR x02 -> srh +y17 AND x17 -> fjk +vcp AND khd -> dsj +qcr AND jgd -> wdj +y19 XOR x19 -> ntj +fpb XOR dds -> z37 +x16 XOR y16 -> rqv +y17 XOR x17 -> nhg +ssw OR dsj -> vcj +fpb AND dds -> fqd +pdr OR ggg -> mvq +x38 XOR y38 -> tdj +rqv XOR pht -> z16 +pfw OR kdd -> jqk +x36 AND y36 -> sbn +x33 XOR y33 -> jqp +bns OR gvv -> hhp +y32 XOR x32 -> rbf +y36 XOR x36 -> tww +pgd XOR wjd -> z41 +khd XOR vcp -> z20 +ckb OR sqr -> dwf +x39 XOR y39 -> gqn +wfj XOR vfk -> z04 +qhb OR kjs -> tsf +kdv AND ntj -> wtw +ksd OR hqw -> jvv +x13 XOR y13 -> kdm +y00 XOR x00 -> z00 +jvv XOR jww -> z23 +y12 XOR x12 -> bmw +y22 AND x22 -> ksd +x21 XOR y21 -> jsq +x43 XOR y43 -> qtr +bmg OR dtk -> qcr +gqk OR dvs -> dct +qgg XOR bmw -> z12 +mwb AND sbr -> sdg +mcg AND vcn -> ndb +kkg OR bdf -> fgd +x04 XOR y04 -> wfj +y22 XOR x22 -> dpc +skh XOR vgs -> z26 +ddt XOR nhg -> z17 +bhc XOR fqq -> z35 +tdj XOR pnh -> z38 +kdm XOR qhc -> z13 +y31 AND x31 -> sqr +vvg AND pvj -> sjr +rbf AND dwf -> npr +qtr AND tfq -> bns +y18 XOR x18 -> hrm +chn AND jjk -> ggv +y28 AND x28 -> kjs +mtw OR fdv -> z33 +nff XOR dgg -> z07 +y10 AND x10 -> kkg +jsq AND vcj -> z21 +vft OR fjk -> jhc +qtr XOR tfq -> z43 +gqn AND sjq -> kdd +y15 XOR x15 -> fmd +wfj AND vfk -> pdr +y27 XOR x27 -> jgd +y09 AND x09 -> trv +x14 AND y14 -> kjf +tww XOR smj -> z36 +jqk AND vnc -> cpp +nkq OR fqd -> pnh +x06 XOR y06 -> kvq +y01 AND x01 -> gbh +x37 AND y37 -> nkq +x05 XOR y05 -> chf +vdk OR cdm -> sbr +y08 AND x08 -> vdk +sqn XOR fmd -> z15 +x24 AND y24 -> dcj +y25 XOR x25 -> jjk +kbm OR spc -> z45 +hrm XOR jhc -> z18 +pvj XOR vvg -> z42 +wjp XOR dvh -> z10 +mvv OR dcj -> chn +y42 AND x42 -> jrk +tsf XOR kpk -> z29 +vkp XOR jqp -> dqr +y41 XOR x41 -> wjd +y32 AND x32 -> pdf +y26 AND x26 -> vgs +jgd XOR qcr -> z27 +y29 XOR x29 -> kpk +ngs OR pwn -> vfk +dpc AND sjk -> hqw +qhc AND kdm -> crm +rqv AND pht -> nqp +x39 AND y39 -> z39 +thj XOR fgd -> z11 +tjr OR ghw -> tcv +gkk XOR pvt -> z03 +y27 AND x27 -> fjc +y28 XOR x28 -> bdw +gbh OR ndb -> pqk +tqd XOR hkv -> z31 +dct AND hsg -> cdm +fbd OR cgq -> fqq +x05 AND y05 -> ntm +sbn OR ddk -> dds +y40 XOR x40 -> vnc +y20 XOR x20 -> khd +x08 XOR y08 -> hsg +tdj AND pnh -> jrg diff --git a/inputs/input24_test b/inputs/input24_test new file mode 100644 index 0000000..de04a48 --- /dev/null +++ b/inputs/input24_test @@ -0,0 +1,10 @@ +x00: 1 +x01: 1 +x02: 1 +y00: 0 +y01: 1 +y02: 0 + +x00 AND y00 -> z00 +x01 XOR y01 -> z01 +x02 OR y02 -> z02 \ No newline at end of file diff --git a/inputs/input24_test2 b/inputs/input24_test2 new file mode 100644 index 0000000..09fb230 --- /dev/null +++ b/inputs/input24_test2 @@ -0,0 +1,47 @@ +x00: 1 +x01: 0 +x02: 1 +x03: 1 +x04: 0 +y00: 1 +y01: 1 +y02: 1 +y03: 1 +y04: 1 + +ntg XOR fgs -> mjb +y02 OR x01 -> tnw +kwq OR kpj -> z05 +x00 OR x03 -> fst +tgd XOR rvg -> z01 +vdt OR tnw -> bfw +bfw AND frj -> z10 +ffh OR nrd -> bqk +y00 AND y03 -> djm +y03 OR y00 -> psh +bqk OR frj -> z08 +tnw OR fst -> frj +gnj AND tgd -> z11 +bfw XOR mjb -> z00 +x03 OR x00 -> vdt +gnj AND wpb -> z02 +x04 AND y00 -> kjc +djm OR pbm -> qhw +nrd AND vdt -> hwm +kjc AND fst -> rvg +y04 OR y02 -> fgs +y01 AND x02 -> pbm +ntg OR kjc -> kwq +psh XOR fgs -> tgd +qhw XOR tgd -> z09 +pbm OR djm -> kpj +x03 XOR y03 -> ffh +x00 XOR y04 -> ntg +bfw OR bqk -> z06 +nrd XOR fgs -> wpb +frj XOR qhw -> z04 +bqk OR frj -> z07 +y03 OR x01 -> nrd +hwm AND bqk -> z03 +tgd XOR rvg -> z12 +tnw OR pbm -> gnj \ No newline at end of file