diff --git a/2022/d11/ex1/ex1.py b/2022/d11/ex1/ex1.py new file mode 100755 index 0000000..85f2344 --- /dev/null +++ b/2022/d11/ex1/ex1.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python + +import dataclasses +import sys +from collections import Counter, deque +from typing import Literal, Optional + + +@dataclasses.dataclass +class Operation: + lhs: Optional[int] + operator: Literal["+", "*"] + rhs: Optional[int] + + def __call__(self, old: int) -> int: + lhs = old if self.lhs is None else self.lhs + rhs = old if self.rhs is None else self.rhs + + if self.operator == "*": + return lhs * rhs + if self.operator == "+": + return lhs + rhs + assert False + + @classmethod + def from_input(cls, input: str) -> "Operation": + assert input.startswith(" Operation: new = ") + lhs, op, rhs = input.split()[-3:] + + assert op in ("+", "*") # Sanity check + + return cls( + None if lhs == "old" else int(lhs), + op, # type: ignore + None if rhs == "old" else int(rhs), + ) + + +@dataclasses.dataclass +class Monkey: + items: deque[int] + operation: Operation + test_divisor: int + transfer: dict[bool, int] + + @classmethod + def from_input(cls, input: list[str]) -> "Monkey": + # Sanity checks + assert input[0].startswith("Monkey ") + assert "divisible by" in input[3] + assert "true" in input[4] + assert "false" in input[5] + + items = deque( + int(n) for n in input[1].removeprefix(" Starting items: ").split(",") + ) + operation = Operation.from_input(input[2]) + divisor = int(input[3].split()[-1]) + transfer = { + True: int(input[4].split()[-1]), + False: int(input[5].split()[-1]), + } + return Monkey(items, operation, divisor, transfer) + + +def solve(input: list[str]) -> int: + def do_round(monkeys: list[Monkey], counts: dict[int, int]) -> None: + for i, monkey in enumerate(monkeys): + counts[i] += len(monkey.items) + while monkey.items: + item = monkey.items.popleft() + item = monkey.operation(item) + item //= 3 + target = monkey.transfer[(item % monkey.test_divisor) == 0] + monkeys[target].items.append(item) + + monkeys = [Monkey.from_input(monkey_spec.splitlines()) for monkey_spec in input] + counts: Counter[int] = Counter() + + for _ in range(20): + do_round(monkeys, counts) + + ((_, a), (_, b)) = counts.most_common(2) + return a * b + + +def main() -> None: + input = sys.stdin.read().split("\n\n") + print(solve(input)) + + +if __name__ == "__main__": + main() diff --git a/2022/d11/ex1/input b/2022/d11/ex1/input new file mode 100644 index 0000000..0fa9bdd --- /dev/null +++ b/2022/d11/ex1/input @@ -0,0 +1,55 @@ +Monkey 0: + Starting items: 50, 70, 54, 83, 52, 78 + Operation: new = old * 3 + Test: divisible by 11 + If true: throw to monkey 2 + If false: throw to monkey 7 + +Monkey 1: + Starting items: 71, 52, 58, 60, 71 + Operation: new = old * old + Test: divisible by 7 + If true: throw to monkey 0 + If false: throw to monkey 2 + +Monkey 2: + Starting items: 66, 56, 56, 94, 60, 86, 73 + Operation: new = old + 1 + Test: divisible by 3 + If true: throw to monkey 7 + If false: throw to monkey 5 + +Monkey 3: + Starting items: 83, 99 + Operation: new = old + 8 + Test: divisible by 5 + If true: throw to monkey 6 + If false: throw to monkey 4 + +Monkey 4: + Starting items: 98, 98, 79 + Operation: new = old + 3 + Test: divisible by 17 + If true: throw to monkey 1 + If false: throw to monkey 0 + +Monkey 5: + Starting items: 76 + Operation: new = old + 4 + Test: divisible by 13 + If true: throw to monkey 6 + If false: throw to monkey 3 + +Monkey 6: + Starting items: 52, 51, 84, 54 + Operation: new = old * 17 + Test: divisible by 19 + If true: throw to monkey 4 + If false: throw to monkey 1 + +Monkey 7: + Starting items: 82, 86, 91, 79, 94, 92, 59, 94 + Operation: new = old + 7 + Test: divisible by 2 + If true: throw to monkey 5 + If false: throw to monkey 3 diff --git a/2022/d11/ex2/ex2.py b/2022/d11/ex2/ex2.py new file mode 100755 index 0000000..dd947f1 --- /dev/null +++ b/2022/d11/ex2/ex2.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python + +import dataclasses +import functools +import sys +from collections import Counter, deque +from typing import Literal, Optional + + +@dataclasses.dataclass +class Operation: + lhs: Optional[int] + operator: Literal["+", "*"] + rhs: Optional[int] + + def __call__(self, old: int) -> int: + lhs = old if self.lhs is None else self.lhs + rhs = old if self.rhs is None else self.rhs + + if self.operator == "*": + return lhs * rhs + if self.operator == "+": + return lhs + rhs + assert False + + @classmethod + def from_input(cls, input: str) -> "Operation": + assert input.startswith(" Operation: new = ") + lhs, op, rhs = input.split()[-3:] + + assert op in ("+", "*") # Sanity check + + return cls( + None if lhs == "old" else int(lhs), + op, # type: ignore + None if rhs == "old" else int(rhs), + ) + + +@dataclasses.dataclass +class Monkey: + items: deque[int] + operation: Operation + test_divisor: int + transfer: dict[bool, int] + + @classmethod + def from_input(cls, input: list[str]) -> "Monkey": + # Sanity checks + assert input[0].startswith("Monkey ") + assert "divisible by" in input[3] + assert "true" in input[4] + assert "false" in input[5] + + items = deque( + int(n) for n in input[1].removeprefix(" Starting items: ").split(",") + ) + operation = Operation.from_input(input[2]) + divisor = int(input[3].split()[-1]) + transfer = { + True: int(input[4].split()[-1]), + False: int(input[5].split()[-1]), + } + return Monkey(items, operation, divisor, transfer) + + +def solve(input: list[str]) -> int: + def do_round(monkeys: list[Monkey], counts: dict[int, int]) -> None: + modulo = functools.reduce( + lambda lhs, rhs: lhs * rhs, + (monkey.test_divisor for monkey in monkeys), + ) + for i, monkey in enumerate(monkeys): + counts[i] += len(monkey.items) + while monkey.items: + item = monkey.items.popleft() + item = monkey.operation(item) + item %= modulo # Keep a reasonable range on this value + target = monkey.transfer[(item % monkey.test_divisor) == 0] + monkeys[target].items.append(item) + + monkeys = [Monkey.from_input(monkey_spec.splitlines()) for monkey_spec in input] + counts: Counter[int] = Counter() + + for _ in range(10_000): + do_round(monkeys, counts) + + ((_, a), (_, b)) = counts.most_common(2) + return a * b + + +def main() -> None: + input = sys.stdin.read().split("\n\n") + print(solve(input)) + + +if __name__ == "__main__": + main() diff --git a/2022/d11/ex2/input b/2022/d11/ex2/input new file mode 100644 index 0000000..0fa9bdd --- /dev/null +++ b/2022/d11/ex2/input @@ -0,0 +1,55 @@ +Monkey 0: + Starting items: 50, 70, 54, 83, 52, 78 + Operation: new = old * 3 + Test: divisible by 11 + If true: throw to monkey 2 + If false: throw to monkey 7 + +Monkey 1: + Starting items: 71, 52, 58, 60, 71 + Operation: new = old * old + Test: divisible by 7 + If true: throw to monkey 0 + If false: throw to monkey 2 + +Monkey 2: + Starting items: 66, 56, 56, 94, 60, 86, 73 + Operation: new = old + 1 + Test: divisible by 3 + If true: throw to monkey 7 + If false: throw to monkey 5 + +Monkey 3: + Starting items: 83, 99 + Operation: new = old + 8 + Test: divisible by 5 + If true: throw to monkey 6 + If false: throw to monkey 4 + +Monkey 4: + Starting items: 98, 98, 79 + Operation: new = old + 3 + Test: divisible by 17 + If true: throw to monkey 1 + If false: throw to monkey 0 + +Monkey 5: + Starting items: 76 + Operation: new = old + 4 + Test: divisible by 13 + If true: throw to monkey 6 + If false: throw to monkey 3 + +Monkey 6: + Starting items: 52, 51, 84, 54 + Operation: new = old * 17 + Test: divisible by 19 + If true: throw to monkey 4 + If false: throw to monkey 1 + +Monkey 7: + Starting items: 82, 86, 91, 79, 94, 92, 59, 94 + Operation: new = old + 7 + Test: divisible by 2 + If true: throw to monkey 5 + If false: throw to monkey 3