• # En Python, avec un parseur d'opération

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

    from __future__ import annotations
    import itertools
    import math
    import re
    from typing import Callable, Iterable, Iterator, List, Tuple
    class Test:
     def __init__(self, div: int, rcpt1: int, rcpt2: int):
     self.div = div
     self.rcpt1 = rcpt1
     self.rcpt2 = rcpt2
     def __call__(self, n: int):
     if n % self.div == 0:
     return self.rcpt1
     else:
     return self.rcpt2
    class Monkey:
     def __init__(self, number: int, items: List[int], op: Callable[[int], int], test: Test) -> None:
     self.number = number
     self.items = items
     self.op = op
     self.test = test
     self.activity = 0
     _re_monkey = re.compile(r'^Monkey (\d+):?\n?$')
     _re_items = re.compile(r'^\s*Starting items: (.*)\n?$')
     _re_op = re.compile(r'^\s*Operation: (.*)\n?$')
     _re_test = re.compile(r'^\s*Test: divisible by (\d+)\n?$')
     _re_true = re.compile(r'^\s*If true: throw to monkey (\d+)\n?$')
     _re_false = re.compile(r'^\s*If false: throw to monkey (\d+)\n?$')
     @classmethod
     def import_lines(class_, lines: Iterable[str]) -> Monkey:
     lines = iter(lines)
     number = int(class_._re_monkey.match(next(lines)).group(1))
     items = [int(word) for word in class_._re_items.match(next(lines)).group(1).split(', ')]
     op = import_op(class_._re_op.match(next(lines)).group(1))
     div = int(class_._re_test.match(next(lines)).group(1))
     rcpt1 = int(class_._re_true.match(next(lines)).group(1))
     rcpt2 = int(class_._re_false.match(next(lines)).group(1))
     test = Test(div, rcpt1, rcpt2)
     return class_(number, items, op, test)
    def group_key() -> Callable[[str], int]:
     key = 0
     def aux(line: str):
     nonlocal key
     if line.rstrip() == '':
     key += 1
     return key
     return aux
    def group_lines(lines: Iterable[str]) -> Iterable[Iterable[str]]:
     for _, group in itertools.groupby(lines, group_key()):
     yield (line for line in group if line.rstrip() != "")
    _re_op_unary = re.compile("^new = old ([+*]) (\d+)$")
    _re_op_binary = re.compile("^new = old ([+*]) old$")
    def import_op(line: str) -> Callable[[int], int]:
     if (m := _re_op_unary.match(line)) is not None:
     arg = int(m.group(2))
     if m.group(1) == '+':
     return lambda old: old + arg
     if m.group(1) == '*':
     return lambda old: old * arg
     raise ValueError("unrecognized operator")
     if (m := _re_op_binary.match(line)) is not None:
     if m.group(1) == '+':
     return lambda old: 2 * old
     if m.group(1) == '*':
     return lambda old: old ** 2
     raise ValueError("unrecognized operator")
     raise ValueError("unrecognized operation arity")
    class Game:
     def __init__(self, monkeys: dict[int, Monkey]) -> None:
     self.monkeys = monkeys
     def round(self) -> None:
     for monkey in self.monkeys.values():
     for item in monkey.items:
     item = monkey.op(item)
     item //= 3
     rcpt = monkey.test(item)
     monkey.activity += 1
     self.monkeys[rcpt].items.append(item)
     monkey.items = []
     @classmethod
     def import_lines(class_, lines: Iterable[str]) -> Game:
     monkeys = {}
     for group in group_lines(lines):
     monkey = Monkey.import_lines(group)
     monkeys[monkey.number] = monkey
     return class_(monkeys)
     def business(self) -> int:
     activity = sorted(monkey.activity for monkey in self.monkeys.values())
     return activity[-1] * activity[-2]
    class Game2(Game):
     def __init__(self, *args, **kwargs) -> None:
     super().__init__(*args, **kwargs)
     self.div = math.lcm(*(monkey.test.div for monkey in self.monkeys.values()))
     def round(self) -> None:
     for monkey in self.monkeys.values():
     for item in monkey.items:
     item = monkey.op(item) % self.div
     rcpt = monkey.test(item)
     monkey.activity += 1
     self.monkeys[rcpt].items.append(item)
     monkey.items = []
    def solve1(lines: Iterable[str]) -> int:
     """Solve part 1 of today's puzzle"""
     game = Game.import_lines(lines)
     for _ in range(20):
     game.round()
     return game.business()
    def solve2(lines: Iterable[str]) -> int:
     """Solve part 2 of today's puzzle"""
     game = Game2.import_lines(lines)
     for _ in range(10000):
     game.round()
     return game.business()