From 1278ebe7f04ae2134a8e177a41beec1c3952b80e Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Tue, 24 Dec 2024 10:32:01 -0500 Subject: [PATCH] 2024: d24: ex2: add solution --- 2024/d24/ex2/ex2.py | 107 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100755 2024/d24/ex2/ex2.py diff --git a/2024/d24/ex2/ex2.py b/2024/d24/ex2/ex2.py new file mode 100755 index 0000000..0d54e57 --- /dev/null +++ b/2024/d24/ex2/ex2.py @@ -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()