2024: d24: ex2: add solution

This commit is contained in:
Bruno BELANYI 2024-12-24 10:32:01 -05:00
parent 2930ef2b0f
commit 1278ebe7f0

107
2024/d24/ex2/ex2.py Executable file
View file

@ -0,0 +1,107 @@
#!/usr/bin/env python
import enum
import sys
from collections import defaultdict
from typing import NamedTuple
class Op(enum.StrEnum):
AND = "AND"
OR = "OR"
XOR = "XOR"
def apply(self, lhs: bool, rhs: bool) -> bool:
match self:
case Op.AND:
return lhs & rhs
case Op.OR:
return lhs | rhs
case Op.XOR:
return lhs ^ rhs
class Gate(NamedTuple):
lhs: str
op: Op
rhs: str
def solve(input: str) -> str:
def parse_values(input: list[str]) -> dict[str, bool]:
return {
name: bool(int(val)) for name, val in map(lambda s: s.split(": "), input)
}
def parse_operation(input: str) -> tuple[str, Gate]:
lhs, op, rhs, _, name = input.split()
return name, Gate(lhs, Op(op), rhs)
def parse_circuit(input: list[str]) -> dict[str, Gate]:
return {name: gate for name, gate in map(parse_operation, input)}
def parse(input: str) -> tuple[dict[str, bool], dict[str, Gate]]:
values, circuit = input.split("\n\n")
return parse_values(values.splitlines()), parse_circuit(circuit.splitlines())
def downstream_ops(circuit: dict[str, Gate]) -> dict[str, set[Op]]:
res: dict[str, set[Op]] = defaultdict(set)
for gate in circuit.values():
res[gate.lhs].add(gate.op)
res[gate.rhs].add(gate.op)
return res
def match_adders(circuit: dict[str, Gate]) -> set[str]:
def validate_and(wire: str, wire_ops: dict[str, set[Op]]) -> bool:
gate = circuit[wire]
assert gate.op == Op.AND # Sanity check
# AND must lead into an OR carry, unless it reads the first bit
return wire_ops[wire] == {Op.OR} or {gate.lhs, gate.rhs} == {"x00", "y00"}
def validate_or(wire: str, wire_ops: dict[str, set[Op]]) -> bool:
gate = circuit[wire]
assert gate.op == Op.OR # Sanity check
# OR outputs the last bit as a direct carry, or into an AND and XOR
return wire == "z45" or wire_ops[wire] == {Op.AND, Op.XOR}
def validate_xor(wire: str, wire_ops: dict[str, set[Op]]) -> bool:
gate = circuit[wire]
assert gate.op == Op.XOR # Sanity check
inputs = {gate.lhs, gate.rhs}
has_input = all(any(i.startswith(w) for i in inputs) for w in ("x", "y"))
# If lowest bit, XOR has no carry and outputs directly
if inputs == {"x00", "y00"} and wire == "z00":
return True
# Otherwise, if it read input bits, it outputs to a carry XOR
if has_input and Op.XOR in wire_ops[wire]:
return True
# If it doesn't read input bits, it must output to Z
if not has_input and wire.startswith("z"):
return True
return False
def validate(wire: str, wire_ops: dict[str, set[Op]]) -> bool:
return {
Op.AND: validate_and,
Op.OR: validate_or,
Op.XOR: validate_xor,
}[circuit[wire].op](wire, wire_ops)
wire_ops = downstream_ops(circuit)
return {wire for wire in circuit if not validate(wire, wire_ops)}
_, circuit = parse(input)
return ",".join(sorted(match_adders(circuit)))
def main() -> None:
input = sys.stdin.read()
print(solve(input))
if __name__ == "__main__":
main()