Advertisement
kupuguy

Advent Of Code 2024 Day 24

Dec 24th, 2024
210
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 3.53 KB | Source Code | 0 0
  1. from pathlib import Path
  2.  
  3.  
  4. def parse(input: str) -> dict[frozenset[str], str]:
  5.     wires: dict[str, frozenset[str]] = {}
  6.  
  7.     for line in input.splitlines():
  8.         key, _, value = line.partition(": ")
  9.         if value:
  10.             continue
  11.         operation, _, out = line.partition(" -> ")
  12.         if out:
  13.             left, op, right = operation.split()
  14.             wires[frozenset([left, op, right])] = out
  15.  
  16.     return wires
  17.  
  18.  
  19. INPUT: dict[frozenset[str], str] = parse(Path("input/day24.txt").read_text())
  20. INVERT: dict[set, frozenset[str]] = {v: k for k, v in INPUT.items()}
  21. SWITCH: list[str] = []
  22.  
  23.  
  24. def show(name: str) -> str:
  25.     if name in INVERT:
  26.         ops = "?"
  27.         vars = []
  28.         for s in INVERT[name]:
  29.             if s in ("AND", "OR", "XOR"):
  30.                 ops = s
  31.             else:
  32.                 vars.append(s)
  33.         vars.sort()
  34.  
  35.         return f"({name}: {show(vars[0])} {ops} {show(vars[1])})"
  36.     return name
  37.  
  38.  
  39. def carry_left(z: int) -> str:
  40.     # Returns: carry(n-1) AND (xn XOR yn)
  41.     assert z > 0
  42.  
  43.     right = INPUT[frozenset([f"x{z:02d}", "XOR", f"y{z:02d}"])]
  44.  
  45.     left = carry(z - 1)
  46.     return INPUT[frozenset([left, "AND", right])]
  47.  
  48.  
  49. def carry(z: int) -> str:
  50.     # carry(1) -> x00 AND y00
  51.     # carry(n) -> (carry(n-1) AND (xn XOR yn)) OR (xn AND yn)
  52.     if z == 0:
  53.         res = INPUT[frozenset(["x00", "AND", "y00"])]
  54.         return res
  55.  
  56.     # (carry(z-1) AND (xz XOR yz)) OR (xz AND yz)
  57.     right = INPUT[frozenset([f"x{z:02d}", "AND", f"y{z:02d}"])]
  58.     left = carry_left(z)
  59.     expr = frozenset([left, "OR", right])
  60.     return INPUT[expr]
  61.  
  62.  
  63. def generate(z: int) -> str:
  64.     # print(f"Generate {z}")
  65.     right = INPUT[frozenset([f"x{z:02d}", "XOR", f"y{z:02d}"])]
  66.     if z == 0:
  67.         return right
  68.  
  69.     left = carry(z - 1)
  70.     expr_key = frozenset([left, "XOR", right])
  71.     if expr_key not in INPUT:
  72.         expect = set(INVERT[f"z{z:02d}"])
  73.         unique = expr_key ^ expect
  74.         if unique & set(SWITCH):
  75.             # Avoid infinite recursion
  76.             raise RuntimeError("Confused!")
  77.         print("Try switching", unique)
  78.         switch(*list(unique))
  79.         return generate(z)
  80.  
  81.     expr = INPUT[expr_key]
  82.  
  83.     return expr
  84.  
  85.  
  86. def clean(entry: frozenset[str]) -> tuple[str, set[str]]:
  87.     for x in entry:
  88.         if x in ("AND", "OR", "XOR"):
  89.             return x, set(entry) - {x}
  90.     assert False, f"Missing op {entry}"
  91.  
  92.  
  93. def switch(actual: str, expect: str) -> None:
  94.     SWITCH.extend([expect, actual])
  95.     INPUT[INVERT[actual]], INPUT[INVERT[expect]] = (
  96.         INPUT[INVERT[expect]],
  97.         INPUT[INVERT[actual]],
  98.     )
  99.     INVERT[expect], INVERT[actual] = INVERT[actual], INVERT[expect]
  100.  
  101.  
  102. def fixup(expect: str, actual: str) -> None:
  103.     actual_op, args = clean(INVERT[actual])
  104.     expect_op, exp_args = clean(INVERT[expect])
  105.     if actual_op != expect_op:
  106.         print("Operator change", expect, actual)
  107.         switch(actual, expect)
  108.         return True
  109.  
  110.     # Operators match, args must differ
  111.     for a in args:
  112.         if a in exp_args:
  113.             # One argument in common, fix the other.
  114.             return fixup(list[args - {a}][0], list[exp_args - {a}][0])
  115.  
  116.     print("Couldn't find fixup", expect, actual)
  117.     return False
  118.  
  119.  
  120. zkeys = sorted([k for k in INVERT if k.startswith("z")], reverse=False)[:-1]
  121.  
  122. for expect in zkeys:
  123.     i = int(expect[1:], 10)
  124.     res = generate(i)
  125.     if res != expect:
  126.         fixup(res, expect)
  127.         assert generate(i) == expect
  128.  
  129. SWITCH.sort()
  130. print("Part 2:", ",".join(SWITCH))
  131.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement