Compare commits

...

4 commits

4 changed files with 348 additions and 0 deletions

104
2023/d20/ex1/ex1.py Executable file
View file

@ -0,0 +1,104 @@
#!/usr/bin/env python
import enum
import math
import sys
from collections import defaultdict, deque
from typing import NamedTuple
class ModuleType(enum.StrEnum):
FLIP_FLOP = "%"
CONJUNCTION = "&"
BROADCAST = "broadcaster"
class Pulse(enum.IntEnum):
LOW = 0
HIGH = 1
class Rule(NamedTuple):
module_type: ModuleType
destinations: list[str]
Modules = dict[str, Rule]
def solve(input: list[str]) -> int:
def parse_rule(line: str) -> tuple[str, Rule]:
module, outputs = line.split(" -> ")
name: str
module_type: ModuleType
if module != "broadcaster":
name = module[1:]
module_type = ModuleType(module[0])
else:
name = module
module_type = ModuleType(module)
return name, Rule(module_type, outputs.split(", "))
def parse(input: list[str]) -> Modules:
return dict(map(parse_rule, input))
def compute_inputs(modules: Modules) -> dict[str, list[str]]:
inputs: dict[str, list[str]] = defaultdict(list)
for src, rule in modules.items():
for dst in rule.destinations:
inputs[dst].append(src)
return inputs
def count_pulses(modules: Modules, button_pushes: int) -> tuple[int, int]:
inputs = compute_inputs(modules)
total_pulses = {pulse: 0 for pulse in Pulse}
last_pulse: dict[str, Pulse] = defaultdict(lambda: Pulse.LOW)
for _ in range(button_pushes):
queue: deque[tuple[Pulse, str]] = deque([(Pulse.LOW, "broadcaster")])
while queue:
pulse, name = queue.popleft()
total_pulses[pulse] += 1
mod = modules.get(name)
# This is for unknown outputs
if mod is None:
continue
new_pulse: Pulse
match mod.module_type:
case ModuleType.FLIP_FLOP:
if pulse == Pulse.HIGH:
continue
new_pulse = Pulse(1 - last_pulse[name])
case ModuleType.CONJUNCTION:
high_inputs = all(
last_pulse[input] == Pulse.HIGH for input in inputs[name]
)
new_pulse = Pulse.LOW if high_inputs else Pulse.HIGH
case ModuleType.BROADCAST:
new_pulse = pulse
last_pulse[name] = new_pulse
for dst in mod.destinations:
queue.append((new_pulse, dst))
return total_pulses[Pulse.LOW], total_pulses[Pulse.HIGH]
modules = parse(input)
return math.prod(count_pulses(modules, 1000))
def main() -> None:
input = sys.stdin.read().splitlines()
print(solve(input))
if __name__ == "__main__":
main()

58
2023/d20/ex1/input Normal file
View file

@ -0,0 +1,58 @@
%fl -> tf, gz
%xb -> hl, tl
%mq -> tf, fl
%px -> hl, tm
%dp -> xv
broadcaster -> js, ng, lb, gr
&ql -> rx
%gk -> hm
%vp -> vf, sn
%fp -> xb
&lr -> ss, rm, dc, js, gk, dp, bq
%xl -> gx, lr
%xx -> hb
%cb -> jg
&hl -> nj, lb, tl, xx, hb, fp, mf
%vr -> tf, hq
%bq -> gk
%jg -> qn
%hb -> qk
%qk -> hs, hl
%gz -> tf
%rm -> hj
&tf -> cb, jg, fz, gr, zj, qn, kb
%qn -> td
%js -> lr, dc
%qb -> nc
%zj -> vr
%td -> tf, zj
%tl -> kg
%gx -> lr
%hm -> lr, rd
&fh -> ql
%nj -> xx
%hq -> kb, tf
%kg -> px, hl
%dc -> dp
%vf -> th, sn
&mf -> ql
%tm -> hl
&fz -> ql
%xd -> tn, sn
%ng -> vp, sn
%th -> qb
%rd -> xl, lr
%bt -> xd, sn
%tv -> sn
%nl -> bt
%hs -> fp, hl
%xv -> rm, lr
%tn -> sn, tv
%hj -> lr, bq
&ss -> ql
%sd -> nl
&sn -> sd, fh, th, qb, nl, ng, nc
%kb -> mq
%lb -> nj, hl
%gr -> tf, cb
%nc -> sd

128
2023/d20/ex2/ex2.py Executable file
View file

@ -0,0 +1,128 @@
#!/usr/bin/env python
import enum
import itertools
import math
import sys
from collections import defaultdict, deque
from typing import NamedTuple
class ModuleType(enum.StrEnum):
FLIP_FLOP = "%"
CONJUNCTION = "&"
BROADCAST = "broadcaster"
class Pulse(enum.IntEnum):
LOW = 0
HIGH = 1
class Rule(NamedTuple):
module_type: ModuleType
destinations: list[str]
Modules = dict[str, Rule]
def solve(input: list[str]) -> int:
def parse_rule(line: str) -> tuple[str, Rule]:
module, outputs = line.split(" -> ")
name: str
module_type: ModuleType
if module != "broadcaster":
name = module[1:]
module_type = ModuleType(module[0])
else:
name = module
module_type = ModuleType(module)
return name, Rule(module_type, outputs.split(", "))
def parse(input: list[str]) -> Modules:
return dict(map(parse_rule, input))
def compute_inputs(modules: Modules) -> dict[str, list[str]]:
inputs: dict[str, list[str]] = defaultdict(list)
for src, rule in modules.items():
for dst in rule.destinations:
inputs[dst].append(src)
return inputs
def count_buttons(modules: Modules) -> int:
def count_buttons_for(
wanted_src: str,
wanted_dst: str,
pulse_wanted: Pulse,
) -> int:
inputs = compute_inputs(modules)
last_pulse: dict[str, Pulse] = defaultdict(lambda: Pulse.LOW)
for i in itertools.count(start=1):
queue: deque[tuple[Pulse, str]] = deque([(Pulse.LOW, "broadcaster")])
while queue:
pulse, name = queue.popleft()
mod = modules.get(name)
# This is for unknown outputs
if mod is None:
continue
new_pulse: Pulse
match mod.module_type:
case ModuleType.FLIP_FLOP:
if pulse == Pulse.HIGH:
continue
new_pulse = Pulse(1 - last_pulse[name])
case ModuleType.CONJUNCTION:
high_inputs = all(
last_pulse[input] == Pulse.HIGH
for input in inputs[name]
)
new_pulse = Pulse.LOW if high_inputs else Pulse.HIGH
case ModuleType.BROADCAST:
new_pulse = pulse
last_pulse[name] = new_pulse
for dst in mod.destinations:
queue.append((new_pulse, dst))
# We found the pulse we wanted, report the number of button presses
if (
new_pulse == pulse_wanted
and name == wanted_src
and dst == wanted_dst
):
return i
assert False # Sanity check
inputs = compute_inputs(modules)
# The input has a single conjunction leading to "rx"
# So we want to compute when all of *its* inputs are high at the same time
assert len(inputs["rx"]) == 1 # Sanity check
rx_input = inputs["rx"][0]
assert modules[rx_input].module_type == ModuleType.CONJUNCTION # Sanity check
# Shortcut: assume that the high pulse output is cyclic
return math.lcm(
*(count_buttons_for(mod, rx_input, Pulse.HIGH) for mod in inputs[rx_input])
)
modules = parse(input)
return count_buttons(modules)
def main() -> None:
input = sys.stdin.read().splitlines()
print(solve(input))
if __name__ == "__main__":
main()

58
2023/d20/ex2/input Normal file
View file

@ -0,0 +1,58 @@
%fl -> tf, gz
%xb -> hl, tl
%mq -> tf, fl
%px -> hl, tm
%dp -> xv
broadcaster -> js, ng, lb, gr
&ql -> rx
%gk -> hm
%vp -> vf, sn
%fp -> xb
&lr -> ss, rm, dc, js, gk, dp, bq
%xl -> gx, lr
%xx -> hb
%cb -> jg
&hl -> nj, lb, tl, xx, hb, fp, mf
%vr -> tf, hq
%bq -> gk
%jg -> qn
%hb -> qk
%qk -> hs, hl
%gz -> tf
%rm -> hj
&tf -> cb, jg, fz, gr, zj, qn, kb
%qn -> td
%js -> lr, dc
%qb -> nc
%zj -> vr
%td -> tf, zj
%tl -> kg
%gx -> lr
%hm -> lr, rd
&fh -> ql
%nj -> xx
%hq -> kb, tf
%kg -> px, hl
%dc -> dp
%vf -> th, sn
&mf -> ql
%tm -> hl
&fz -> ql
%xd -> tn, sn
%ng -> vp, sn
%th -> qb
%rd -> xl, lr
%bt -> xd, sn
%tv -> sn
%nl -> bt
%hs -> fp, hl
%xv -> rm, lr
%tn -> sn, tv
%hj -> lr, bq
&ss -> ql
%sd -> nl
&sn -> sd, fh, th, qb, nl, ng, nc
%kb -> mq
%lb -> nj, hl
%gr -> tf, cb
%nc -> sd