JonathanGupton

Advent of Code 2022 - Day 11

Dec 11th, 2022 (edited)
501
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 3.73 KB | None | 0 0
  1. from __future__ import annotations
  2. from collections import deque
  3. from dataclasses import dataclass
  4. from functools import reduce
  5. import operator
  6. import re
  7. from typing import Optional, Union
  8.  
  9.  
  10. @dataclass
  11. class WorryTest:
  12.     div_by: int
  13.     if_true: int
  14.     if_false: int
  15.  
  16.  
  17. @dataclass
  18. class Operation:
  19.     op: Union[operator.add, operator.mul]
  20.     rhs: str | int
  21.  
  22.  
  23. @dataclass
  24. class Monkey:
  25.     number: int
  26.     items: deque[int]
  27.     operation: Operation
  28.     test: WorryTest
  29.     inspected: int = 0
  30.  
  31.     def catch(self, item):
  32.         self.items.append(item)
  33.  
  34.  
  35. def inspect_item(item: int, o: Operation) -> int:
  36.     if type(o.rhs) is str:
  37.         return o.op(item, item)
  38.     else:
  39.         return o.op(item, o.rhs)
  40.  
  41.  
  42. def throw_to_target(item: int, t: WorryTest) -> int:
  43.     return t.if_true if (item % t.div_by) == 0 else t.if_false
  44.  
  45.  
  46. def reduce_worry(
  47.     item: int, op: Union[operator.floordiv, operator.mod], amount: int = 3
  48. ) -> int:
  49.     return op(item, amount)
  50.  
  51.  
  52. def parse_monkey(line: str) -> int:
  53.     pattern = r"Monkey (\d):"
  54.     result = re.search(pattern, line)
  55.     return int(result.groups()[0])
  56.  
  57.  
  58. def parse_starting_items(line: str) -> deque[int]:
  59.     pattern = r"(\d+)"
  60.     result = re.findall(pattern, line)
  61.     return deque(list(map(int, result)))
  62.  
  63.  
  64. def parse_operation(line: str) -> Operation:
  65.     op: Optional[Union[operator.add, operator.mul]] = None
  66.     instruction = line.split()[4:]
  67.     match instruction[0]:
  68.         case "+":
  69.             op = operator.add
  70.         case "*":
  71.             op = operator.mul
  72.     try:
  73.         rhs = int(instruction[1])
  74.     except ValueError:
  75.         rhs = instruction[1]
  76.     return Operation(op, rhs)
  77.  
  78.  
  79. def parse_test(lines: list[str]) -> WorryTest:
  80.     div_by = int(lines[0].strip().split()[-1])
  81.     if_true = int(lines[1].strip()[-1])
  82.     if_false = int(lines[2].strip()[-1])
  83.     return WorryTest(div_by, if_true, if_false)
  84.  
  85.  
  86. def parse_file(filepath) -> list[Monkey]:
  87.     monkeys = []
  88.     with open(filepath, "r") as f:
  89.         for chunk in f.read().split("\n\n"):
  90.             text = chunk.splitlines()
  91.             monkey_n = parse_monkey(text[0])
  92.             starting_items = parse_starting_items(text[1])
  93.             operation = parse_operation(text[2])
  94.             test = parse_test(text[3:])
  95.             monkeys.append(Monkey(monkey_n, starting_items, operation, test))
  96.  
  97.     return monkeys
  98.  
  99.  
  100. def play_round_one(monkeys: list[Monkey]):
  101.     for i, monkey in enumerate(monkeys):
  102.         while monkey.items:
  103.             item = monkey.items.popleft()
  104.             item = inspect_item(item, monkey.operation)
  105.             monkey.inspected += 1
  106.             item = reduce_worry(item, operator.floordiv, 3)
  107.             target = throw_to_target(item, monkey.test)
  108.             monkeys[target].catch(item)
  109.  
  110.  
  111. def play_round_two(monkeys: list[Monkey], modulo: int):
  112.     for i, monkey in enumerate(monkeys):
  113.         while monkey.items:
  114.             item = monkey.items.popleft()
  115.             item = inspect_item(item, monkey.operation)
  116.             monkey.inspected += 1
  117.             item = reduce_worry(item, operator.mod, modulo)
  118.             target = throw_to_target(item, monkey.test)
  119.             monkeys[target].catch(item)
  120.  
  121.  
  122. def part_one(filepath) -> int:
  123.     monkeys = parse_file(filepath)
  124.     for _ in range(20):
  125.         play_round_one(monkeys)
  126.     n_inspected = sorted([m.inspected for m in monkeys])
  127.     return n_inspected[-1] * n_inspected[-2]
  128.  
  129.  
  130. def part_two(filepath) -> int:
  131.     monkeys = parse_file(filepath)
  132.     modulo = reduce(operator.mul, [m.test.div_by for m in monkeys])
  133.     for _ in range(10_000):
  134.         play_round_two(monkeys, modulo)
  135.     n_inspected = sorted([m.inspected for m in monkeys])
  136.     return n_inspected[-1] * n_inspected[-2]
  137.  
Advertisement
Add Comment
Please, Sign In to add comment