Skip to content

Commit e949ce9

Browse files
committed
Sort of functional implementation of 2024 day 24
1 parent 4882428 commit e949ce9

File tree

4 files changed

+183
-0
lines changed

4 files changed

+183
-0
lines changed

2024/bonus/24todot.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import fileinput
2+
3+
print("digraph day24 {")
4+
5+
for line in fileinput.input():
6+
parts = line.split(" ")
7+
if len(parts) != 5:
8+
continue
9+
10+
first, op, second, _, result = parts
11+
print(f'{first}{second}{op} [label="{op}"];')
12+
print(f"{first} -> {first}{second}{op} -> {result};")
13+
print(f"{second} -> {first}{second}{op};")
14+
15+
print("}")

2024/src/aoc/days/day24.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import functools
2+
import re
3+
4+
from . import SeparateRunner
5+
6+
7+
def parse_input(input: str) -> tuple[dict[str, int], dict[str, tuple[str, str, str]]]:
8+
variable_part, rules_part = input.strip().split("\n\n")
9+
10+
variables = {}
11+
12+
for line in variable_part.splitlines():
13+
variable, value = line.split(": ")
14+
variables[variable] = int(value)
15+
16+
rules = {}
17+
18+
for first, op, second, result in re.findall(
19+
r"(\w+) (XOR|OR|AND) (\w+) -> (\w+)", rules_part
20+
):
21+
rules[result] = (first, op, second)
22+
23+
return variables, rules
24+
25+
26+
class DayRunner(SeparateRunner):
27+
@classmethod
28+
def part1(cls, input: str) -> int:
29+
variables, rules = parse_input(input)
30+
31+
@functools.cache
32+
def get_value(variable: str) -> int:
33+
if variable in variables:
34+
return variables[variable]
35+
36+
first, op, second = rules[variable]
37+
first_v = get_value(first)
38+
second_v = get_value(second)
39+
40+
match op:
41+
case "AND":
42+
return first_v & second_v
43+
case "OR":
44+
return first_v | second_v
45+
case "XOR":
46+
return first_v ^ second_v
47+
48+
result = 0
49+
for variable in reversed(sorted(rules)):
50+
if not variable.startswith("z"):
51+
continue
52+
result = result * 2 + get_value(variable)
53+
54+
return result
55+
56+
@classmethod
57+
def part2(cls, input: str) -> str:
58+
variables, rules = parse_input(input)
59+
60+
max_bit = int(
61+
max(variable for variable in rules if variable.startswith("z"))[1:]
62+
)
63+
64+
def find_invalid(output: str, pattern) -> set[str]:
65+
if pattern is None:
66+
return set()
67+
68+
if output in rules:
69+
left, op, right = rules[output]
70+
elif output == pattern:
71+
return set()
72+
else:
73+
return {output}
74+
75+
pop, pleft, pright = pattern
76+
77+
if op != pop:
78+
return {output}
79+
80+
wrong_normal = find_invalid(left, pleft) | find_invalid(right, pright)
81+
wrong_mirror = find_invalid(left, pright) | find_invalid(right, pleft)
82+
83+
least_wrong = min(wrong_mirror, wrong_normal, key=len)
84+
85+
return least_wrong
86+
87+
# First one is a half adder, that's a simple pattern
88+
invalid = find_invalid("z00", ["XOR", "x00", "y00"])
89+
# Second one is missing a reference to the before-previous adder, so it's a
90+
# slightly different patterns
91+
invalid |= find_invalid(
92+
"z01", ["XOR", ["AND", "x00", "y00"], ["XOR", "x01", "y01"]]
93+
)
94+
95+
for n in range(2, max_bit):
96+
xcurr = f"x{n:02}"
97+
ycurr = f"y{n:02}"
98+
zcurr = f"z{n:02}"
99+
xprev = f"x{n-1:02}"
100+
yprev = f"y{n-1:02}"
101+
102+
invalid |= find_invalid(
103+
zcurr,
104+
[
105+
"XOR",
106+
["XOR", xcurr, ycurr],
107+
["OR", ["AND", xprev, yprev], ["AND", ["XOR", xprev, yprev], None]],
108+
],
109+
)
110+
111+
# This code somehow believes `ktp` is invalid, but it's fine on closer
112+
# inspection. Will figure that out later.
113+
114+
return ",".join(sorted(invalid))

2024/tests/samples/24.txt

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
x00: 1
2+
x01: 0
3+
x02: 1
4+
x03: 1
5+
x04: 0
6+
y00: 1
7+
y01: 1
8+
y02: 1
9+
y03: 1
10+
y04: 1
11+
12+
ntg XOR fgs -> mjb
13+
y02 OR x01 -> tnw
14+
kwq OR kpj -> z05
15+
x00 OR x03 -> fst
16+
tgd XOR rvg -> z01
17+
vdt OR tnw -> bfw
18+
bfw AND frj -> z10
19+
ffh OR nrd -> bqk
20+
y00 AND y03 -> djm
21+
y03 OR y00 -> psh
22+
bqk OR frj -> z08
23+
tnw OR fst -> frj
24+
gnj AND tgd -> z11
25+
bfw XOR mjb -> z00
26+
x03 OR x00 -> vdt
27+
gnj AND wpb -> z02
28+
x04 AND y00 -> kjc
29+
djm OR pbm -> qhw
30+
nrd AND vdt -> hwm
31+
kjc AND fst -> rvg
32+
y04 OR y02 -> fgs
33+
y01 AND x02 -> pbm
34+
ntg OR kjc -> kwq
35+
psh XOR fgs -> tgd
36+
qhw XOR tgd -> z09
37+
pbm OR djm -> kpj
38+
x03 XOR y03 -> ffh
39+
x00 XOR y04 -> ntg
40+
bfw OR bqk -> z06
41+
nrd XOR fgs -> wpb
42+
frj XOR qhw -> z04
43+
bqk OR frj -> z07
44+
y03 OR x01 -> nrd
45+
hwm AND bqk -> z03
46+
tgd XOR rvg -> z12
47+
tnw OR pbm -> gnj

2024/tests/test_day24.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from aoc.days.day24 import DayRunner
2+
3+
from . import get_data
4+
5+
6+
def test_sample_part1() -> None:
7+
assert DayRunner.part1(get_data(24)) == 2024

0 commit comments

Comments
 (0)