from collections import defaultdict, deque import math from pprint import pprint import sys import re modules = {} conjunctions = {} flipflops = {} firstit = {} cycles = {} def simulate(it): queue = deque([(None, "button", 0)]) pulses = [0, 0] while queue: (source, target, level) = queue.popleft() if target not in modules: continue (type, destinations) = modules[target] if type == "": pass elif type == "%": if level == 1: continue flipflops[target] = not flipflops[target] level = int(flipflops[target]) elif type == "&": conjunction = conjunctions[target] conjunction[source] = level level = 0 if all(conjunction.values()) else 1 else: continue for destination in destinations: queue.append((target, destination, level)) pulses[level] += 1 if destination == "dt" and level and target not in cycles: if target not in firstit: firstit[target] = it continue cycle = it - firstit[target] cycles[target] = (firstit[target], cycle) if len(cycles) == len(conjunctions["dt"]): lcm = math.lcm(*(v[1] for v in cycles.values())) return (pulses, lcm) return (pulses, None) def main(): has_rx = False with open(sys.argv[1]) as f: for line in f: (type, name, destinations) = re.match(r"([%\&]?)(.+) -> (.+)", line).groups() destinations = destinations.split(", ") modules[name] = (type, destinations) if "rx" in destinations: has_rx = True if type == "%": flipflops[name] = False elif type == "&": conjunctions[name] = {} modules["button"] = ("", ["broadcaster"]) for (name, (type, dests)) in modules.items(): for dest in dests: if dest not in modules or modules[dest][0] != "&": continue conjunctions[dest][name] = 0 (lows, highs) = (0, 0) (part1, part2) = (None, None) it = 0 while part1 is None or part2 is None: ([low, high], cycle) = simulate(it) if it < 1000: lows += low highs += high elif it == 1000: part1 = lows * highs if not has_rx: break if cycle: part2 = cycle it += 1 print(part1, part2) if __name__ == "__main__": main()