hhoppe

Advent of code 2024 day 24 part 2

Dec 24th, 2024 (edited)
224
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 3.18 KB | None | 0 0
  1. def day24_parse_vals_and_expressions(s):
  2.   p1, p2 = s.split('\n\n')
  3.   vals = {k: int(v) for line in p1.splitlines() for k, v in (line.split(': '),)}
  4.  
  5.   @dataclasses.dataclass
  6.   class Expression:
  7.     op: str
  8.     srcs: tuple[str, str]
  9.  
  10.   expressions = {
  11.       dst: Expression(op, (src1, src2))
  12.       for line in p2.splitlines()
  13.       for src1, op, src2, dst in (re.findall(r'\w+', line),)
  14.   }
  15.  
  16.   return vals, expressions
  17.  
  18.  
  19. def day24_part2(s, *, correct_to=1000, return_pairs=False):
  20.   _, expressions = day24_parse_vals_and_expressions(s)
  21.   n_z = sum(1 for dst in expressions if dst[0] == 'z')
  22.  
  23.   def orient(expr):  # Heuristically assign a canonical order for two expression operands.
  24.     expr2 = expressions.get(expr.srcs[1], None)
  25.     if expr.srcs[1][0] == 'x' or (expr2 and (expr2.op == 'XOR' or expr2.srcs[0][0] in 'xy')):
  26.       expr.srcs = expr.srcs[::-1]
  27.  
  28.   def tree_is_correct(i):
  29.     e = expressions[f'z{i:02}']
  30.     if e.op == 'XOR' and all(n in expressions for n in e.srcs):
  31.       e1, e2 = expressions[e.srcs[0]], expressions[e.srcs[1]]
  32.       if e1.op == 'XOR' and e1.srcs[0][0] == 'x':
  33.         if e2.op == 'OR' and all(n in expressions for n in e2.srcs):
  34.           e21, e22 = expressions[e2.srcs[0]], expressions[e2.srcs[1]]
  35.           if e21.op == 'AND' and e21.srcs[0][0] == 'x':
  36.             last_e = expressions[f'z{i - 1:02}']
  37.             if i <= 2 or e22.op == 'AND' and set(e22.srcs) == set(last_e.srcs):
  38.               return True
  39.     return False
  40.  
  41.   for expr in expressions.values():
  42.     orient(expr)
  43.   known_correct = set[str]()
  44.   pairs = []
  45.  
  46.   for i in range(n_z):
  47.  
  48.     def local_nodes(i: int) -> Iterator[str]:
  49.       seen = known_correct | set(expressions[f'z{i - 1:02}'].srcs)
  50.  
  51.       def recurse(dst: str) -> Iterator[str]:
  52.         seen.add(dst)
  53.         yield dst
  54.         expr = expressions[dst]
  55.         for n in expr.srcs:
  56.           if n in expressions and n not in seen:
  57.             yield from recurse(n)
  58.  
  59.       yield from recurse(f'z{i:02}')
  60.  
  61.     def locally_orient(i: int) -> None:
  62.       for dst in local_nodes(i):
  63.         orient(expressions[dst])
  64.  
  65.     def swap(dst1: str, dst2: str) -> None:
  66.       expressions[dst1], expressions[dst2] = expressions[dst2], expressions[dst1]
  67.       locally_orient(i)
  68.       locally_orient(i + 1)
  69.  
  70.     if 2 <= i < n_z - 2:
  71.  
  72.       if i <= correct_to and not tree_is_correct(i):
  73.         # print(f'Problem at z{i:02}')
  74.         # We consider exchanging each pair within a set of local nodes, specifically those
  75.         # contributing to the outputs i and i + 1, as this seems to be the puzzle pattern.
  76.         dsts = set(local_nodes(i)) | set(local_nodes(i + 1))
  77.         for dst1, dst2 in itertools.combinations(dsts, 2):
  78.           swap(dst1, dst2)
  79.           if tree_is_correct(i) and tree_is_correct(i + 1):
  80.             # print(f'At {i=}, found a correcting pair {dst1}, {dst2}.')
  81.             pairs.append((dst1, dst2))
  82.             break
  83.           swap(dst1, dst2)  # Swap back since it didn't solve it.
  84.         else:
  85.           raise AssertionError(f'Could not find repair for z{i:02}')
  86.  
  87.       known_correct.update(local_nodes(i))
  88.  
  89.   return pairs if return_pairs else ','.join(sorted(itertools.chain.from_iterable(pairs)))
Advertisement
Add Comment
Please, Sign In to add comment