Skip to content

Commit 1048b76

Browse files
authored
Add 2023/20 py
1 parent 7784701 commit 1048b76

18 files changed

+481
-2
lines changed

.devcontainer/devcontainer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"packages": [
66
"cloc",
77
"direnv",
8+
"graphviz",
89
"multitime",
910
"octave",
1011
"shellcheck",

.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"npos",
3131
"nprev",
3232
"pdir",
33-
"ppos"
33+
"ppos",
34+
"pprev"
3435
],
3536
}

2023/20/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Advent of Code - 2023 Day 20
2+
Here are my solutions to this puzzle.
3+
4+
* Problem instructions: [adventofcode.com/2023/day/20](https://adventofcode.com/2023/day/20)
5+
* Input: [adventofcode.com/2023/day/20/input](https://adventofcode.com/2023/day/20/input)
6+
7+
Fetch input by exporting `$AOC_SESSION` in your shell and then:
8+
```bash
9+
curl -OJLsb session=$AOC_SESSION adventofcode.com/2023/day/20/input
10+
```
11+
12+
or run `fetch_input.sh` in this directory.

2023/20/fetch_input.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/usr/bin/env sh
2+
# Make sure $AOC_SESSION is exported before running this script.
3+
4+
curl --remote-name --remote-header-name --silent --fail -A 'https://erikw.me/contact' --cookie "session=$AOC_SESSION" "https://adventofcode.com/2023/day/20/input"
5+
test "$?" -eq 0 && echo "Fetched input" && exit 0 || echo "Failed to fetch input" && exit 1

2023/20/input1.0

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
broadcaster -> a, b, c
2+
%a -> b
3+
%b -> c
4+
%c -> inv
5+
&inv -> a

2023/20/input1.1

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
broadcaster -> a
2+
%a -> inv, con
3+
&inv -> b
4+
%b -> con
5+
&con -> output

2023/20/instructions.url

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[InternetShortcut]
2+
URL = https://adventofcode.com/2023/day/20

2023/20/mermaid.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
#!/usr/bin/env python3
2+
import fileinput
3+
from pprint import pprint
4+
from python_mermaid.diagram import MermaidDiagram, Node, Link
5+
6+
7+
MODULE_BUTTON = "button"
8+
MODULE_BROADCASTER = "broadcaster"
9+
MODULE_FINAL = "rx"
10+
11+
STATE_ON = True
12+
STATE_OFF = False
13+
14+
TYPE_UNTYPED = '-'
15+
TYPE_FLIPFLOP = '%'
16+
TYPE_CONJUNCTION = '&'
17+
18+
PULSE_HIGH = True
19+
PULSE_LOW = False
20+
21+
22+
def read_module_configuration():
23+
mod_conf = {
24+
MODULE_BUTTON: {"type": TYPE_UNTYPED, "dest_names": [MODULE_BROADCASTER]}
25+
}
26+
for line in fileinput.input():
27+
part_name, part_destinations = line.rstrip("\n").split(" -> ")
28+
29+
if part_name[0] in [TYPE_FLIPFLOP, TYPE_CONJUNCTION]:
30+
type = part_name[0]
31+
name = part_name[1:]
32+
else:
33+
type = TYPE_UNTYPED
34+
name = part_name
35+
destination_names = part_destinations.split(", ")
36+
37+
if type == TYPE_UNTYPED:
38+
mod_conf[name] = {"type": type, "dest_names": destination_names}
39+
elif type == TYPE_FLIPFLOP:
40+
mod_conf[name] = {"type": type, "dest_names": destination_names, "state": STATE_OFF}
41+
elif type == TYPE_CONJUNCTION:
42+
mod_conf[name] = {"type": type, "dest_names": destination_names, "input_mem": {}}
43+
44+
for name in mod_conf:
45+
for dest_name in mod_conf[name]["dest_names"]:
46+
if dest_name in mod_conf and mod_conf[dest_name]["type"] == TYPE_CONJUNCTION:
47+
mod_conf[dest_name]["input_mem"][name] = PULSE_LOW
48+
49+
return mod_conf
50+
51+
52+
def push_button(mod_conf):
53+
final_mod_pulses = [0, 0]
54+
pulses = deque([(MODULE_BUTTON, PULSE_LOW, MODULE_BROADCASTER)])
55+
56+
while pulses:
57+
name_from, pulse, name_to = pulses.popleft()
58+
# print(f"{name_from} -{"high" if pulse else "low"}-> {name_to}")
59+
60+
if name_to == MODULE_FINAL:
61+
final_mod_pulses[bool(pulse)] += 1
62+
63+
if name_to not in mod_conf:
64+
continue
65+
66+
module = mod_conf[name_to]
67+
if module["type"] == TYPE_UNTYPED:
68+
for dest_name in module["dest_names"]:
69+
pulses.append((name_to, pulse, dest_name))
70+
elif module["type"] == TYPE_FLIPFLOP:
71+
if pulse == PULSE_LOW:
72+
module["state"] = not module["state"]
73+
pulse_out = module["state"]
74+
for dest_name in module["dest_names"]:
75+
pulses.append((name_to, pulse_out, dest_name))
76+
elif module["type"] == TYPE_CONJUNCTION:
77+
module["input_mem"][name_from] = pulse
78+
pulse_out = PULSE_LOW if all(module["input_mem"].values()) else PULSE_HIGH
79+
for dest_name in module["dest_names"]:
80+
pulses.append((name_to, pulse_out, dest_name))
81+
82+
83+
return final_mod_pulses
84+
85+
def main():
86+
mod_conf = read_module_configuration()
87+
pprint(mod_conf)
88+
89+
nodes = {
90+
MODULE_FINAL: Node(MODULE_FINAL, f'{TYPE_UNTYPED}{MODULE_FINAL}')
91+
}
92+
links = []
93+
for name, module in mod_conf.items():
94+
nodes[name] = Node(name, f'{module["type"]}{name}')
95+
96+
for name, module in mod_conf.items():
97+
for dest_name in module["dest_names"]:
98+
links.append(Link(nodes[name], nodes[dest_name]))
99+
100+
chart = MermaidDiagram(
101+
title="Module Configuration",
102+
nodes=nodes.values(),
103+
links=links
104+
)
105+
print(chart) # Paste output at https://mermaid.live/
106+
# More specifically accessible here:
107+
# https://mermaid.live/edit#pako:eNptVrmS2zoQ_BUVqqxI2lpJK-2agRM7deTM5gtI8RIPkIIgFq2t_ffXMwMQdNlZNwgM5uoB39W5z3IVqe12G2t7sW0erb732b3NV197XVzKu0nspdex5h2lSYZqFWsz_YrV1kyx-i_W6d3aXtOCIF4cUyx8GlMmA5NBSGlB1qVlUtX0paqZPDIij0y-GP5imJiSiCmZ2IKILcT0wKYHuXTiS8Uty8QK0RURXTG5nYnczkzOTM5CrOYzEkLBpgsxXfG2SrYZjse44PjLIF9S0yfZObnZ3HBCApUI2f5D7OsbJULfhLAVLVY6jrCTCKeRyDTKzZwvI_mynBUrWSnZ21K8rR9E6oeQlkkrKSK_1qO4U1-J1FcmDZ1ZN3Km5EtLubRkd0pxJ2OSCWnIwKdGDLQNkbaRe25cCgmutfxFSq5bDlvc6bjKncsOm34ULpNEUld_Nm3E9JW3XWWbYUeNONrypa1cWnRcvk6qTAbWZzFQ8JlCzljOm3UNxG0ySptURNaVkJbz1oo7DW9r5EvKBlIx0HJ92nohjBWU82W17ASIQxbhD7QhGAWGNASjIlCGYORzxvAFIhGMwKCRsG5KwYgECgn2PYYQIBbBKMuM6ew4CUZVIRvBqMmMyY6u3NmJBBTWPYbUoaWw7jF6FbISjOaaMboBCnN7UhJY2GNcTqCNGUMNkJq7q_pDaS7A4h-L6J-_F6G_vxchPcgz-OAx2gRKFYwJNmO0wozTBca8mHG5xAs70CIEH_I_45q0H2L3mOxMY1j3uKG61yHnHmO0YTi49TPNhuB__XD-1wGTzboN_o8m9GF9dTWaaES4BBYBQ8gzzhbrlGSPx8VZzGBMl5CHsgg2Pab8Zz52sul8wJjHlAl58xhPBgaOqwXtcTrCJJgx2dRtiKtzMeJRweARfOUWCvXymPtwce-MDU2jYN9jPCAYTM5-EzD77PzEUzJjOlt0Dl9pWgUte4yRPWM8NmFPGXC32ENy8JhiLMaQf49Zj0OwOVahlzwm-1UV5obH5KfHeMxnTDrymOriMc2rdtFXTRVy6zEeLIzTkCuPMd4xWf1-tVGluWQqsuaeb1SXmy4hqt5jvVrFylZ5l8cqAswSejNi_YEzQ6J_9n3nj5n-XlYqKpL2BnYfssTm3y4J_nDmLcnd9j9-6_N8JNdZbr72d21VtNsd2KaK3tWkou3u7fXp-Hx4Ox1f97vDaaN-q2j__PS2-_z6utudDi-HE7Z8bNSDndg_HY6n0_7t8-748rw_vJyOG5VnF9ub7_JHxj9mH_8DJh0Aag
108+
109+
110+
111+
if __name__ == '__main__':
112+
main()

2023/20/output1.0

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
32000000

2023/20/output1.1

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
11687500

2023/20/output2.0

Whitespace-only changes.

2023/20/part1.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
#!/usr/bin/env python3
2+
import fileinput
3+
from pprint import pprint
4+
from collections import deque
5+
from math import prod
6+
7+
PUSHES = 1000
8+
9+
MODULE_BUTTON = "button"
10+
MODULE_BROADCASTER = "broadcaster"
11+
12+
STATE_ON = True
13+
STATE_OFF = False
14+
15+
TYPE_UNTYPED = '-'
16+
TYPE_FLIPFLOP = '%'
17+
TYPE_CONJUNCTION = '&'
18+
19+
PULSE_HIGH = True
20+
PULSE_LOW = False
21+
22+
23+
def read_module_configuration():
24+
mod_conf = {
25+
MODULE_BUTTON: {"type": TYPE_UNTYPED, "dest_names": [MODULE_BROADCASTER]}
26+
}
27+
for line in fileinput.input():
28+
part_name, part_destinations = line.rstrip("\n").split(" -> ")
29+
30+
if part_name[0] in [TYPE_FLIPFLOP, TYPE_CONJUNCTION]:
31+
type = part_name[0]
32+
name = part_name[1:]
33+
else:
34+
type = TYPE_UNTYPED
35+
name = part_name
36+
destination_names = part_destinations.split(", ")
37+
38+
if type == TYPE_UNTYPED:
39+
mod_conf[name] = {"type": type, "dest_names": destination_names}
40+
elif type == TYPE_FLIPFLOP:
41+
mod_conf[name] = {"type": type, "dest_names": destination_names, "state": STATE_OFF}
42+
elif type == TYPE_CONJUNCTION:
43+
mod_conf[name] = {"type": type, "dest_names": destination_names, "input_mem": {}}
44+
45+
for name in mod_conf:
46+
for dest_name in mod_conf[name]["dest_names"]:
47+
if dest_name in mod_conf and mod_conf[dest_name]["type"] == TYPE_CONJUNCTION:
48+
mod_conf[dest_name]["input_mem"][name] = PULSE_LOW
49+
50+
return mod_conf
51+
52+
53+
def push_button(mod_conf):
54+
nbr_pulses = [0, 0]
55+
pulses = deque([(MODULE_BUTTON, PULSE_LOW, MODULE_BROADCASTER)])
56+
57+
while pulses:
58+
name_from, pulse, name_to = pulses.popleft()
59+
# print(f"{name_from} -{"high" if pulse else "low"}-> {name_to}")
60+
61+
nbr_pulses[bool(pulse)] += 1
62+
63+
if name_to not in mod_conf:
64+
continue
65+
66+
module = mod_conf[name_to]
67+
if module["type"] == TYPE_UNTYPED:
68+
for dest_name in module["dest_names"]:
69+
pulses.append((name_to, pulse, dest_name))
70+
elif module["type"] == TYPE_FLIPFLOP:
71+
if pulse == PULSE_LOW:
72+
module["state"] = not module["state"]
73+
pulse_out = module["state"]
74+
for dest_name in module["dest_names"]:
75+
pulses.append((name_to, pulse_out, dest_name))
76+
elif module["type"] == TYPE_CONJUNCTION:
77+
module["input_mem"][name_from] = pulse
78+
pulse_out = PULSE_LOW if all(module["input_mem"].values()) else PULSE_HIGH
79+
for dest_name in module["dest_names"]:
80+
pulses.append((name_to, pulse_out, dest_name))
81+
82+
83+
return nbr_pulses
84+
85+
def main():
86+
mod_conf = read_module_configuration()
87+
88+
pulses_tot = [0, 0]
89+
for _ in range(PUSHES):
90+
pulses = push_button(mod_conf)
91+
pulses_tot[0] += pulses[0]
92+
pulses_tot[1] += pulses[1]
93+
94+
print(prod(pulses_tot))
95+
96+
if __name__ == '__main__':
97+
main()

2023/20/part2.py

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
#!/usr/bin/env python3
2+
import fileinput
3+
from collections import deque
4+
import math
5+
6+
7+
MODULE_BUTTON = "button"
8+
MODULE_BROADCASTER = "broadcaster"
9+
MODULE_FINAL = "rx"
10+
11+
STATE_ON = True
12+
STATE_OFF = False
13+
14+
TYPE_UNTYPED = 'u'
15+
TYPE_FLIPFLOP = '%'
16+
TYPE_CONJUNCTION = '&'
17+
18+
PULSE_HIGH = True
19+
PULSE_LOW = False
20+
21+
22+
def read_module_configuration():
23+
mod_conf = {
24+
MODULE_BUTTON: {"type": TYPE_UNTYPED, "dest_names": [MODULE_BROADCASTER], "input_names": []},
25+
MODULE_FINAL: {"type": TYPE_UNTYPED, "dest_names": [], "input_names": []},
26+
}
27+
for line in fileinput.input():
28+
part_name, part_destinations = line.rstrip("\n").split(" -> ")
29+
30+
if part_name[0] in [TYPE_FLIPFLOP, TYPE_CONJUNCTION]:
31+
type = part_name[0]
32+
name = part_name[1:]
33+
else:
34+
type = TYPE_UNTYPED
35+
name = part_name
36+
destination_names = part_destinations.split(", ")
37+
38+
if type == TYPE_UNTYPED:
39+
mod_conf[name] = {"type": type, "dest_names": destination_names, "input_names": []}
40+
elif type == TYPE_FLIPFLOP:
41+
mod_conf[name] = {"type": type, "dest_names": destination_names, "state": STATE_OFF, "input_names": []}
42+
elif type == TYPE_CONJUNCTION:
43+
mod_conf[name] = {"type": type, "dest_names": destination_names, "input_mem": {}, "input_names": []}
44+
45+
for name in mod_conf:
46+
for dest_name in mod_conf[name]["dest_names"]:
47+
if dest_name in mod_conf and mod_conf[dest_name]["type"] == TYPE_CONJUNCTION:
48+
mod_conf[dest_name]["input_mem"][name] = PULSE_LOW
49+
if dest_name in mod_conf:
50+
mod_conf[dest_name]["input_names"].append(name)
51+
52+
return mod_conf
53+
54+
55+
def find_min_presses(mod_conf):
56+
"""
57+
# h/t https://www.reddit.com/r/adventofcode/comments/18mmfxb/comment/ke5a5fc/?utm_source=share&utm_medium=web3x&utm_name=web3xcss&utm_term=1&utm_content=share_button
58+
# With manual analysis of output from mermaid.py, it is concluded that for my input:
59+
# * rx is connected as: [&lr, &nl, &vr, &gt] -> &jq -> rx
60+
# * further it can be seen that those earlier 4 conjunctions all go to a different sub graph, namely the 4 subgraphs branching from module broadcaster.
61+
# * assume those sub-graphs have their own cycles, and that the first time a low input arrives at them, the number of presses up til that point is the cycle (this is universaily not the case, as the presses leading up to the first low signal could be an init sequence before the network has stabilized). Why low signal? Because all last nodes are inverters 4& -> 1& -> rx, meaning low to first level mean low to rx.
62+
# * thus period(rx) = lcd(period(&lr), period(&nl), period(&vr), period(&jq))
63+
"""
64+
assert len(mod_conf[MODULE_FINAL]["input_names"]) == 1
65+
module_prev = mod_conf[MODULE_FINAL]["input_names"][0]
66+
67+
assert len(mod_conf[module_prev]["input_names"]) == 4
68+
modules_pprev = mod_conf[module_prev]["input_names"]
69+
70+
presses = 0
71+
periods = {}
72+
while len(periods) < len(modules_pprev):
73+
presses += 1
74+
75+
final_mod_pulses = [0, 0]
76+
pulses = deque([(MODULE_BUTTON, PULSE_LOW, MODULE_BROADCASTER)])
77+
78+
while pulses:
79+
name_from, pulse, name_to = pulses.popleft()
80+
81+
if name_to in modules_pprev and pulse == PULSE_LOW and name_to not in periods:
82+
periods[name_to] = presses
83+
84+
if name_to == MODULE_FINAL:
85+
final_mod_pulses[bool(pulse)] += 1
86+
87+
if name_to not in mod_conf:
88+
continue
89+
90+
module = mod_conf[name_to]
91+
if module["type"] == TYPE_UNTYPED:
92+
for dest_name in module["dest_names"]:
93+
pulses.append((name_to, pulse, dest_name))
94+
elif module["type"] == TYPE_FLIPFLOP:
95+
if pulse == PULSE_LOW:
96+
module["state"] = not module["state"]
97+
pulse_out = module["state"]
98+
for dest_name in module["dest_names"]:
99+
pulses.append((name_to, pulse_out, dest_name))
100+
elif module["type"] == TYPE_CONJUNCTION:
101+
module["input_mem"][name_from] = pulse
102+
pulse_out = PULSE_LOW if all(module["input_mem"].values()) else PULSE_HIGH
103+
for dest_name in module["dest_names"]:
104+
pulses.append((name_to, pulse_out, dest_name))
105+
106+
107+
return math.lcm(*periods.values())
108+
109+
def main():
110+
mod_conf = read_module_configuration()
111+
presses = find_min_presses(mod_conf)
112+
print(presses)
113+
114+
if __name__ == '__main__':
115+
main()

0 commit comments

Comments
 (0)