168 lines
4.6 KiB
Python
168 lines
4.6 KiB
Python
|
#!/usr/bin/env python
|
||
|
|
||
|
import dataclasses
|
||
|
import enum
|
||
|
import operator
|
||
|
import sys
|
||
|
from typing import Optional, Union
|
||
|
|
||
|
Num = Union[int, "MathObserver"]
|
||
|
|
||
|
|
||
|
class Operator(str, enum.Enum):
|
||
|
ADD = "+"
|
||
|
SUB = "-"
|
||
|
MUL = "*"
|
||
|
DIV = "/"
|
||
|
|
||
|
def __call__(self, lhs: Num, rhs: Num) -> Num:
|
||
|
OPERATIONS = {
|
||
|
self.ADD: operator.add,
|
||
|
self.SUB: operator.sub,
|
||
|
self.MUL: operator.mul,
|
||
|
self.DIV: operator.floordiv,
|
||
|
}
|
||
|
return OPERATIONS[self](lhs, rhs)
|
||
|
|
||
|
def reverse(self, lhs: Num, rhs: Num) -> Num:
|
||
|
OPERATIONS = {
|
||
|
self.ADD: Operator.SUB,
|
||
|
self.SUB: Operator.ADD,
|
||
|
self.MUL: Operator.DIV,
|
||
|
self.DIV: Operator.MUL,
|
||
|
}
|
||
|
return OPERATIONS[self](lhs, rhs)
|
||
|
|
||
|
|
||
|
@dataclasses.dataclass
|
||
|
class MathObserver:
|
||
|
_operations: list[tuple[Operator, int, bool]] = dataclasses.field(
|
||
|
default_factory=list
|
||
|
)
|
||
|
_target: Optional[int] = dataclasses.field(default=None, init=False)
|
||
|
|
||
|
def __add__(self, rhs: int) -> "MathObserver":
|
||
|
return MathObserver(self._operations + [(Operator.ADD, rhs, False)])
|
||
|
|
||
|
def __radd__(self, lhs: int) -> "MathObserver":
|
||
|
return MathObserver(self._operations + [(Operator.ADD, lhs, False)])
|
||
|
|
||
|
def __mul__(self, rhs: int) -> "MathObserver":
|
||
|
return MathObserver(self._operations + [(Operator.MUL, rhs, False)])
|
||
|
|
||
|
def __rmul__(self, lhs: int) -> "MathObserver":
|
||
|
return MathObserver(self._operations + [(Operator.MUL, lhs, False)])
|
||
|
|
||
|
def __sub__(self, rhs: int) -> "MathObserver":
|
||
|
return MathObserver(self._operations + [(Operator.SUB, rhs, False)])
|
||
|
|
||
|
def __rsub__(self, lhs: int) -> "MathObserver":
|
||
|
return MathObserver(self._operations + [(Operator.SUB, lhs, True)])
|
||
|
|
||
|
def __floordiv__(self, rhs: int) -> "MathObserver":
|
||
|
return MathObserver(self._operations + [(Operator.DIV, rhs, False)])
|
||
|
|
||
|
def __rfloordiv__(self, lhs: int) -> "MathObserver":
|
||
|
return MathObserver(self._operations + [(Operator.DIV, lhs, True)])
|
||
|
|
||
|
def __eq__(self, rhs: object) -> bool:
|
||
|
return self._record_eq(rhs)
|
||
|
|
||
|
def __req__(self, lhs: object) -> bool:
|
||
|
return self._record_eq(lhs)
|
||
|
|
||
|
def _record_eq(self, value: object) -> bool:
|
||
|
# Sanity checks
|
||
|
assert isinstance(value, int)
|
||
|
assert self._target is None
|
||
|
self._target = value
|
||
|
return True
|
||
|
|
||
|
def resolve(self) -> int:
|
||
|
assert self._target is not None # Sanity check
|
||
|
target: int = self._target
|
||
|
|
||
|
for op, n, is_assymetric_rhs in reversed(self._operations):
|
||
|
if is_assymetric_rhs:
|
||
|
target = op(n, target) # type: ignore
|
||
|
else:
|
||
|
target = op.reverse(target, n) # type: ignore
|
||
|
|
||
|
return target
|
||
|
|
||
|
|
||
|
class Monkey:
|
||
|
def get_value(self, monkeys: dict[str, "Monkey"]) -> Num:
|
||
|
raise NotImplementedError
|
||
|
|
||
|
|
||
|
@dataclasses.dataclass
|
||
|
class YellerMonkey(Monkey):
|
||
|
value: int
|
||
|
|
||
|
def get_value(self, monkeys: dict[str, "Monkey"]) -> int:
|
||
|
return self.value
|
||
|
|
||
|
|
||
|
@dataclasses.dataclass
|
||
|
class MathMonkey(Monkey):
|
||
|
lhs: str
|
||
|
op: Operator
|
||
|
rhs: str
|
||
|
_value: Optional[Num] = dataclasses.field(default=None, init=False)
|
||
|
|
||
|
def get_value(self, monkeys: dict[str, "Monkey"]) -> Num:
|
||
|
if self._value is None:
|
||
|
self._value = self.op(
|
||
|
monkeys[self.lhs].get_value(monkeys),
|
||
|
monkeys[self.rhs].get_value(monkeys),
|
||
|
)
|
||
|
return self._value
|
||
|
|
||
|
|
||
|
class Human(Monkey):
|
||
|
def get_value(self, monkeys: dict[str, "Monkey"]) -> MathObserver:
|
||
|
return MathObserver()
|
||
|
|
||
|
|
||
|
def solve(input: list[str]) -> int:
|
||
|
def parse_monkey(input: str) -> tuple[str, Monkey]:
|
||
|
name, value = input.split(": ")
|
||
|
|
||
|
monkey: Monkey
|
||
|
match value.split():
|
||
|
case [lhs, op, rhs]:
|
||
|
monkey = MathMonkey(lhs, Operator(op), rhs)
|
||
|
case [n]:
|
||
|
monkey = YellerMonkey(int(n))
|
||
|
case _:
|
||
|
assert False # Sanity check
|
||
|
|
||
|
return name, monkey
|
||
|
|
||
|
monkeys = dict(map(parse_monkey, input))
|
||
|
|
||
|
monkeys["humn"] = Human()
|
||
|
|
||
|
root = monkeys["root"]
|
||
|
assert isinstance(root, MathMonkey) # Sanity check
|
||
|
|
||
|
lhs, rhs = monkeys[root.lhs], monkeys[root.rhs]
|
||
|
assert lhs.get_value(monkeys) == rhs.get_value(monkeys)
|
||
|
|
||
|
for monkey in (lhs, rhs):
|
||
|
value = monkey.get_value(monkeys)
|
||
|
if not isinstance(value, MathObserver):
|
||
|
continue
|
||
|
return value.resolve()
|
||
|
assert False # Sanity check
|
||
|
|
||
|
|
||
|
def main() -> None:
|
||
|
input = sys.stdin.read().splitlines()
|
||
|
print(solve(input))
|
||
|
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
main()
|