• # Python, fait maison

    Posté par (site web personnel) . En réponse au message Avent du Code, jour 21. Évalué à 4.

    Bon, fait maison, c'est à dire sans eval ni bibliothèque externe. On construit un arbre, pour la première partie on calcule récursivement la valeur du singe racine. Pour la deuxième partie, on force la valeur du singe racine, ce qui force récursivement la valeur des singes en-dessous, jusqu'à forcer la valeur de l'humain.

    from __future__ import annotations
    import re
    from collections import defaultdict
    from enum import Enum
    from typing import Callable, Dict, Iterable, Optional, Sequence, Set, Tuple
    class Operation(Enum):
     ADD = '+'
     SUB = '-'
     MUL = '*'
     DIV = '/'
     @property
     def op(self) -> Callable[[int, int], int]:
     if self is self.ADD:
     return int.__add__
     if self is self.SUB:
     return int.__sub__
     if self is self.MUL:
     return int.__mul__
     if self is self.DIV:
     return int.__floordiv__
     assert False # we covered all cases
     @property
     def rev1(self) -> Callable[[int, int], int]:
     """Given a and b, return x so self.op(x, a) = b"""
     if self is self.ADD:
     return int.__sub__
     if self is self.SUB:
     return int.__add__
     if self is self.MUL:
     return int.__floordiv__
     if self is self.DIV:
     return int.__mul__
     assert False # we covered all cases
     @property
     def rev2(self) -> Callable[[int, int], int]:
     """Given a and b, return x so self.op(a, x) = b"""
     if self is self.ADD:
     return int.__sub__
     if self is self.SUB:
     return lambda v, a: a - v
     if self is self.MUL:
     return int.__floordiv__
     if self is self.DIV:
     return lambda v, a: a // v
     assert False # we covered all cases
    class Monkey:
     monkeys: Dict[str, Monkey] = {}
     def __init__(self, name: str,
     value: Optional[int] = None,
     others: Optional[Sequence[str]] = None,
     operation: Optional[Operation] = None) -> None:
     if (others is None) != (operation is None):
     raise ValueError(
     "others and operation must be both None or both defined")
     if (value is None) == (others is None):
     raise ValueError(
     "value xor (others and operation) must be defined")
     self.name = name
     self._value = value
     self.others = others
     self.operation = operation
     # Register self in class directory
     self.monkeys[name] = self
     @property
     def value(self) -> int:
     if self._value is not None:
     return self._value
     if self.others is None or self.operation is None:
     raise ValueError(
     "cannot compute value of a monkey w/o others or operation")
     return self.operation.op(
     *[self.monkeys[other].value for other in self.others])
     @value.setter
     def value(self, v: int) -> None:
     if self._value is not None:
     # This monkey has a fixed value
     raise ValueError("cannot change value of a valued monkey")
     if self.others is None or self.operation is None:
     # This monkey has a free value, we just have to set it
     self._value = v
     return
     # The free monkey is somewhere under self
     a: Optional[int] = None # value of first sub-monkey
     b: Optional[int] = None # value of second sub-monkey
     try:
     a = self.monkeys[self.others[0]].value
     except ValueError:
     # This failed because the value-free monkey is here, not a problem
     pass
     try:
     b = self.monkeys[self.others[1]].value
     except ValueError:
     # This failed because the value-free monkey is here, not a problem
     pass
     if a is None and b is not None:
     a = self.operation.rev1(v, b)
     self.monkeys[self.others[0]].value = a
     return
     if a is not None and b is None:
     b = self.operation.rev2(v, a)
     self.monkeys[self.others[1]].value = b
     return
     # We do not expect to have two or no value-free monkey under us
     assert False
     _monkey_re = re.compile(r"^(\w+): (.*)\n?$")
     _operation_re = re.compile(r"^(\w+) ([-+/*]) (\w+)\n?$")
     @classmethod
     def import_line(class_, line: str) -> Monkey:
     match = class_._monkey_re.match(line)
     if match is None:
     raise ValueError("invalid monkey description '{}'".format(line))
     name = match.group(1)
     line = match.group(2)
     if line.isdigit():
     value = int(line)
     return class_(name, value=value)
     match = class_._operation_re.match(line)
     if match is None:
     raise ValueError("invalid monkey operation '{}'".format(line))
     others = (match.group(1), match.group(3))
     op = Operation(match.group(2))
     return class_(name, others=others, operation=op)
    def solve_both(lines: Iterable[str]) -> Tuple[int, int]:
     """Solve both parts of today's puzzle"""
     for line in lines:
     _ = Monkey.import_line(line)
     root = Monkey.monkeys['root']
     humn = Monkey.monkeys['humn']
     # Get original root monkey value
     result1 = root.value
     # Correct root monkey operation
     root.operation = Operation.SUB
     # Unset our own value so it becomes the free one
     humn._value = None
     # Force root monkey value: this will cascade to eventually set
     # our value
     root.value = 0
     result2 = humn.value
     return result1, result2