-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathaoc202320.py
117 lines (94 loc) · 3.5 KB
/
aoc202320.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
"""AoC 20, 2023: Pulse Propagation."""
# Standard library imports
import collections
import itertools
import math
import pathlib
import string
import sys
def parse_data(puzzle_input):
"""Parse input."""
modules = {}
for line in puzzle_input.split("\n"):
full_name, _, targets = line.partition(" -> ")
name = "".join(ch for ch in full_name if ch in string.ascii_letters)
kind = full_name.removesuffix(name)
modules[name] = (kind, targets.split(", "))
return modules
def part1(modules):
"""Solve part 1."""
pulses = []
state = initial_state(modules)
for _ in range(1000):
state, pulse_count, _ = push_button(modules, state)
pulses.append(pulse_count)
return math.prod([sum(pulse) for pulse in list(zip(*pulses))])
def part2(modules):
"""Solve part 2.
rx only has one input. That input has several conjuntion inputs. To trigger
the pulse to rx, we need all those conjunction modules to line up. Find
their individual cycle lengths and use LCM to find the combined cycle
length.
"""
(feed_to_rx,) = [name for name, (_, targets) in modules.items() if "rx" in targets]
num_inputs = len(
[name for name, (_, targets) in modules.items() if feed_to_rx in targets]
)
cycles = {}
state = initial_state(modules)
for count in itertools.count(start=1):
state, _, trigger = push_button(
modules, state, monitor=feed_to_rx, ignore=list(cycles)
)
if trigger:
cycles[trigger] = count
if len(cycles) == num_inputs:
break
return math.lcm(*cycles.values())
def initial_state(modules):
"""Set up initial state for the given modules."""
return {name: False for name, (kind, _) in modules.items() if kind == "%"} | {
name: {
input: False for input, (_, targets) in modules.items() if name in targets
}
for name, (kind, _) in modules.items()
if kind == "&"
}
def push_button(modules, state, monitor=None, ignore=None):
"""Simulate one push of a button."""
queue = collections.deque([(("broadcaster", False, ""))])
num_low_pulse, num_high_pulse = 0, 0
while queue:
module, pulse, from_module = queue.popleft()
if pulse:
num_high_pulse += 1
else:
num_low_pulse += 1
if module == monitor and pulse and from_module not in ignore:
return state, (num_low_pulse, num_high_pulse), from_module
kind, targets = modules.get(module, ("X", []))
if kind == "":
for target in targets:
queue.append((target, pulse, module))
elif kind == "%":
if pulse:
continue
state[module] = not state[module]
for target in targets:
queue.append((target, state[module], module))
elif kind == "&":
state[module][from_module] = pulse
out_pulse = not all(state[module].values())
for target in targets:
queue.append((target, out_pulse, module))
return state, (num_low_pulse, num_high_pulse), None
def solve(puzzle_input):
"""Solve the puzzle for the given input."""
data = parse_data(puzzle_input)
yield part1(data)
yield part2(data)
if __name__ == "__main__":
for path in sys.argv[1:]:
print(f"\n{path}:")
solutions = solve(puzzle_input=pathlib.Path(path).read_text().rstrip())
print("\n".join(str(solution) for solution in solutions))