Skip to content

Commit

Permalink
Sort of functional implementation of 2024 day 24
Browse files Browse the repository at this point in the history
  • Loading branch information
bertptrs committed Dec 24, 2024
1 parent 4882428 commit e949ce9
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 0 deletions.
15 changes: 15 additions & 0 deletions 2024/bonus/24todot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import fileinput

print("digraph day24 {")

for line in fileinput.input():
parts = line.split(" ")
if len(parts) != 5:
continue

first, op, second, _, result = parts
print(f'{first}{second}{op} [label="{op}"];')
print(f"{first} -> {first}{second}{op} -> {result};")
print(f"{second} -> {first}{second}{op};")

print("}")
114 changes: 114 additions & 0 deletions 2024/src/aoc/days/day24.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import functools
import re

from . import SeparateRunner


def parse_input(input: str) -> tuple[dict[str, int], dict[str, tuple[str, str, str]]]:
variable_part, rules_part = input.strip().split("\n\n")

variables = {}

for line in variable_part.splitlines():
variable, value = line.split(": ")
variables[variable] = int(value)

rules = {}

for first, op, second, result in re.findall(
r"(\w+) (XOR|OR|AND) (\w+) -> (\w+)", rules_part
):
rules[result] = (first, op, second)

return variables, rules


class DayRunner(SeparateRunner):
@classmethod
def part1(cls, input: str) -> int:
variables, rules = parse_input(input)

@functools.cache
def get_value(variable: str) -> int:
if variable in variables:
return variables[variable]

first, op, second = rules[variable]
first_v = get_value(first)
second_v = get_value(second)

match op:
case "AND":
return first_v & second_v
case "OR":
return first_v | second_v
case "XOR":
return first_v ^ second_v

result = 0
for variable in reversed(sorted(rules)):
if not variable.startswith("z"):
continue
result = result * 2 + get_value(variable)

return result

@classmethod
def part2(cls, input: str) -> str:
variables, rules = parse_input(input)

max_bit = int(
max(variable for variable in rules if variable.startswith("z"))[1:]
)

def find_invalid(output: str, pattern) -> set[str]:
if pattern is None:
return set()

if output in rules:
left, op, right = rules[output]
elif output == pattern:
return set()
else:
return {output}

pop, pleft, pright = pattern

if op != pop:
return {output}

wrong_normal = find_invalid(left, pleft) | find_invalid(right, pright)
wrong_mirror = find_invalid(left, pright) | find_invalid(right, pleft)

least_wrong = min(wrong_mirror, wrong_normal, key=len)

return least_wrong

# First one is a half adder, that's a simple pattern
invalid = find_invalid("z00", ["XOR", "x00", "y00"])
# Second one is missing a reference to the before-previous adder, so it's a
# slightly different patterns
invalid |= find_invalid(
"z01", ["XOR", ["AND", "x00", "y00"], ["XOR", "x01", "y01"]]
)

for n in range(2, max_bit):
xcurr = f"x{n:02}"
ycurr = f"y{n:02}"
zcurr = f"z{n:02}"
xprev = f"x{n-1:02}"
yprev = f"y{n-1:02}"

invalid |= find_invalid(
zcurr,
[
"XOR",
["XOR", xcurr, ycurr],
["OR", ["AND", xprev, yprev], ["AND", ["XOR", xprev, yprev], None]],
],
)

# This code somehow believes `ktp` is invalid, but it's fine on closer
# inspection. Will figure that out later.

return ",".join(sorted(invalid))
47 changes: 47 additions & 0 deletions 2024/tests/samples/24.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
x00: 1
x01: 0
x02: 1
x03: 1
x04: 0
y00: 1
y01: 1
y02: 1
y03: 1
y04: 1

ntg XOR fgs -> mjb
y02 OR x01 -> tnw
kwq OR kpj -> z05
x00 OR x03 -> fst
tgd XOR rvg -> z01
vdt OR tnw -> bfw
bfw AND frj -> z10
ffh OR nrd -> bqk
y00 AND y03 -> djm
y03 OR y00 -> psh
bqk OR frj -> z08
tnw OR fst -> frj
gnj AND tgd -> z11
bfw XOR mjb -> z00
x03 OR x00 -> vdt
gnj AND wpb -> z02
x04 AND y00 -> kjc
djm OR pbm -> qhw
nrd AND vdt -> hwm
kjc AND fst -> rvg
y04 OR y02 -> fgs
y01 AND x02 -> pbm
ntg OR kjc -> kwq
psh XOR fgs -> tgd
qhw XOR tgd -> z09
pbm OR djm -> kpj
x03 XOR y03 -> ffh
x00 XOR y04 -> ntg
bfw OR bqk -> z06
nrd XOR fgs -> wpb
frj XOR qhw -> z04
bqk OR frj -> z07
y03 OR x01 -> nrd
hwm AND bqk -> z03
tgd XOR rvg -> z12
tnw OR pbm -> gnj
7 changes: 7 additions & 0 deletions 2024/tests/test_day24.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from aoc.days.day24 import DayRunner

from . import get_data


def test_sample_part1() -> None:
assert DayRunner.part1(get_data(24)) == 2024

0 comments on commit e949ce9

Please sign in to comment.