diff --git a/2023/day20.py b/2023/day20.py new file mode 100644 index 0000000..6cc1eb8 --- /dev/null +++ b/2023/day20.py @@ -0,0 +1,108 @@ +import fileinput +from collections import defaultdict, Counter, deque +from itertools import count + +from utils import mul + + +# Parse problem input. +broadcaster = [] +flip_flops = defaultdict(list) +conjunctions = defaultdict(list) +graph = defaultdict(list) + +for line in fileinput.input(): + line = line.strip() + if line.startswith('broadcaster'): + outputs = line.split(' -> ')[1] + for out in outputs.split(', '): + broadcaster.append(out) + + elif line.startswith('%'): + mod, outputs = line.split(' -> ') + mod = mod[1:] + for out in outputs.split(', '): + flip_flops[mod].append(out) + graph[out].append(mod) + + elif line.startswith('&'): + mod, outputs = line.split(' -> ') + mod = mod[1:] + for out in outputs.split(', '): + conjunctions[mod].append(out) + graph[out].append(mod) + + +def press_button(state): + # Start the cycle with the broadcaster receiving a + # low pulse from the button. + horizon = deque([('broadcaster', False, 'button')]) + + while horizon: + dst, pulse, frm = horizon.popleft() + + # Output all pulses to be examined. + yield dst, pulse + + if dst == 'broadcaster': + for r in broadcaster: + horizon.append((r, pulse, dst)) + + elif dst in flip_flops: + # If a flip-flop receives a high pulse, do nothing. + if pulse is True: + continue + + # Invert the state of the flip-flop. + STATE[dst] = not STATE[dst] + + # Broadcast the new state to all output modules. + for r in flip_flops[dst]: + horizon.append((r, STATE[dst], dst)) + + elif dst in conjunctions: + # Set memory of this pulse. + STATE[dst][frm] = pulse + + # If memory is all high pulses... + for inp, mem in STATE[dst].items(): + if not mem: + break + else: + # ...send a low pulse to all output modules. + for r in conjunctions[dst]: + horizon.append((r, False, dst)) + + continue + + # Otherwise, send a high pulse to all output modules. + for r in conjunctions[dst]: + horizon.append((r, True, dst)) + + +# Solve problem. +STATE = {k: False for k in flip_flops} + +for mod in conjunctions: + STATE[mod] = {inp: False for inp in graph[mod]} + +# Need to find the aligned cycle of the four modules that +# feed into conjunction X, where X feeds into `rx`. +key_regs = graph[graph['rx'][0]] +cycle_lens = {mod: -1 for mod in key_regs} + +pulse_counts = Counter() + +for cycle in count(start=1): + for module, pulse in press_button(STATE): + if pulse is False and cycle_lens.get(module) == -1: + cycle_lens[module] = cycle + + if cycle <= 1000: + pulse_counts[pulse] += 1 + + if all(v != -1 for v in cycle_lens.values()): + break + +print("Part 1:", mul(pulse_counts.values())) +print("Part 2:", mul(cycle_lens.values()))