···11+import fileinput
22+from collections import defaultdict, Counter, deque
33+from itertools import count
44+55+from utils import mul
66+77+88+# Parse problem input.
99+broadcaster = []
1010+flip_flops = defaultdict(list)
1111+conjunctions = defaultdict(list)
1212+graph = defaultdict(list)
1313+1414+for line in fileinput.input():
1515+ line = line.strip()
1616+ if line.startswith('broadcaster'):
1717+ outputs = line.split(' -> ')[1]
1818+ for out in outputs.split(', '):
1919+ broadcaster.append(out)
2020+2121+ elif line.startswith('%'):
2222+ mod, outputs = line.split(' -> ')
2323+ mod = mod[1:]
2424+ for out in outputs.split(', '):
2525+ flip_flops[mod].append(out)
2626+ graph[out].append(mod)
2727+2828+ elif line.startswith('&'):
2929+ mod, outputs = line.split(' -> ')
3030+ mod = mod[1:]
3131+ for out in outputs.split(', '):
3232+ conjunctions[mod].append(out)
3333+ graph[out].append(mod)
3434+3535+3636+def press_button(state):
3737+ # Start the cycle with the broadcaster receiving a
3838+ # low pulse from the button.
3939+ horizon = deque([('broadcaster', False, 'button')])
4040+4141+ while horizon:
4242+ dst, pulse, frm = horizon.popleft()
4343+4444+ # Output all pulses to be examined.
4545+ yield dst, pulse
4646+4747+ if dst == 'broadcaster':
4848+ for r in broadcaster:
4949+ horizon.append((r, pulse, dst))
5050+5151+ elif dst in flip_flops:
5252+ # If a flip-flop receives a high pulse, do nothing.
5353+ if pulse is True:
5454+ continue
5555+5656+ # Invert the state of the flip-flop.
5757+ STATE[dst] = not STATE[dst]
5858+5959+ # Broadcast the new state to all output modules.
6060+ for r in flip_flops[dst]:
6161+ horizon.append((r, STATE[dst], dst))
6262+6363+ elif dst in conjunctions:
6464+ # Set memory of this pulse.
6565+ STATE[dst][frm] = pulse
6666+6767+ # If memory is all high pulses...
6868+ for inp, mem in STATE[dst].items():
6969+ if not mem:
7070+ break
7171+ else:
7272+ # ...send a low pulse to all output modules.
7373+ for r in conjunctions[dst]:
7474+ horizon.append((r, False, dst))
7575+7676+ continue
7777+7878+ # Otherwise, send a high pulse to all output modules.
7979+ for r in conjunctions[dst]:
8080+ horizon.append((r, True, dst))
8181+8282+8383+# Solve problem.
8484+STATE = {k: False for k in flip_flops}
8585+8686+for mod in conjunctions:
8787+ STATE[mod] = {inp: False for inp in graph[mod]}
8888+8989+# Need to find the aligned cycle of the four modules that
9090+# feed into conjunction X, where X feeds into `rx`.
9191+key_regs = graph[graph['rx'][0]]
9292+cycle_lens = {mod: -1 for mod in key_regs}
9393+9494+pulse_counts = Counter()
9595+9696+for cycle in count(start=1):
9797+ for module, pulse in press_button(STATE):
9898+ if pulse is False and cycle_lens.get(module) == -1:
9999+ cycle_lens[module] = cycle
100100+101101+ if cycle <= 1000:
102102+ pulse_counts[pulse] += 1
103103+104104+ if all(v != -1 for v in cycle_lens.values()):
105105+ break
106106+107107+print("Part 1:", mul(pulse_counts.values()))
108108+print("Part 2:", mul(cycle_lens.values()))