mirror of
https://github.com/bertptrs/adventofcode.git
synced 2025-12-25 21:00:31 +01:00
Sort of functional implementation of 2024 day 24
This commit is contained in:
15
2024/bonus/24todot.py
Normal file
15
2024/bonus/24todot.py
Normal file
@@ -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
2024/src/aoc/days/day24.py
Normal file
114
2024/src/aoc/days/day24.py
Normal file
@@ -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
2024/tests/samples/24.txt
Normal file
47
2024/tests/samples/24.txt
Normal file
@@ -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
2024/tests/test_day24.py
Normal file
7
2024/tests/test_day24.py
Normal file
@@ -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
|
||||||
Reference in New Issue
Block a user