From fff2267f21f35b040f41012d5326d24bd16e73ac Mon Sep 17 00:00:00 2001 From: cupcakearmy Date: Sun, 11 Dec 2022 15:45:20 +0100 Subject: [PATCH] day 11 --- 2022/11/README.md | 28 +++++++++++ 2022/11/python/main.py | 105 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+) create mode 100644 2022/11/README.md create mode 100644 2022/11/python/main.py diff --git a/2022/11/README.md b/2022/11/README.md new file mode 100644 index 0000000..47298d8 --- /dev/null +++ b/2022/11/README.md @@ -0,0 +1,28 @@ +# 11 + +## Part one + +Easy, fun, lets go. + +## Part two + +Performance was the issue. I started moving functions to inline code (reduce readability for less function calls). It did not make a dent. + +Lesson learned: In python functions calls and destructuring are not expensive. + +What helped a bit was: + +- Precalculating if it's a multiplication or addition for each monkey. +- And whether to use the old value or a custom one. + +However that was not nearly enough of a speed boost to get me to the performance needed. + +**Enter math**: We are always diving, so what if I could make the numbers smaller somehow? But everyone uses a different divider? Thats when it struck me! Before passing the item to the next monkey, reduce it with modulo by the biggest common denominator of all monkeys (since the divisions will not influence the other decisions of the other monkeys). And it worked! Speed was no issue anymore ⚡️ + +
+ Solutions +
    +
  1. 58322
  2. +
  3. 13937702909
  4. +
+
diff --git a/2022/11/python/main.py b/2022/11/python/main.py new file mode 100644 index 0000000..de0ec86 --- /dev/null +++ b/2022/11/python/main.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python + +from functools import reduce +from os.path import dirname, join +from typing import Literal + +# Day 11 + +# Common + + +def read_input(filename): + data = join(dirname(__file__), '..', filename) + with open(data) as f: + return f.read().strip() + + +test = read_input('test.txt') +data = read_input('input.txt') + +Old = Literal['old'] +Operator = Literal['+', '*'] +Operation = tuple[Operator, Old | int] + + +class Monkey: + def __init__(self, items: list[int], operation: Operation, test: tuple[int, int, int], chill: bool) -> None: + self.items = items + self.chill = chill + self.inspected: int = 0 + + self.divisible = test[0] + self.true = test[1] + self.false = test[2] + + op, arg = operation + self._old = arg == 'old' + self._add = op == '+' + self._arg = -1 if self._old else int(arg) + + def round(self) -> list[tuple[int, int]]: + l: list[tuple[int, int]] = [] + for value in self.items: + arg = value if self._old else self._arg + value = value + arg if self._add else value * arg + if self.chill: + value = value // 3 + next_monkey = self.true if value % self.divisible == 0 else self.false + l.append((next_monkey, value)) + self.inspected += len(self.items) + self.items.clear() + return l + + def __repr__(self) -> str: + return f"{self.inspected}" + + @staticmethod + def parse(data: str, chill: bool): + lines = data.splitlines() + items = list(map(int, lines[1][18:].split(', '))) + # Operator + op_raw, arg_raw = lines[2][22:].split() + op: Operator = op_raw + arg: Old | int = 'old' if arg_raw == 'old' else int(arg_raw) + operation: Operation = (op, arg) + # Test + divisible = int(lines[3][21:]) + true = int(lines[4][29:]) + false = int(lines[5][30:]) + return Monkey(items, operation, (divisible, true, false), chill) + + +class Game: + def __init__(self, monkeys: list[Monkey]) -> None: + self.monkeys = monkeys + dividers = [m.divisible for m in monkeys] + self.common = reduce(lambda a, b: a*b, dividers, 1) + + def __str__(self) -> str: + return ", ".join(map(str, self.monkeys)) + + def round(self): + for monkey in self.monkeys: + thrown = monkey.round() + for monkey, item in thrown: + self.monkeys[monkey].items.append(item % self.common) + + def flag(self, rounds: int = 20): + for _ in range(rounds): + self.round() + inspected = [monkey.inspected for monkey in self.monkeys] + inspected = (sorted(inspected)[-2:]) + return inspected[0] * inspected[1] + + @staticmethod + def parse(data: str, chill: bool = True): + return Game([Monkey.parse(monkey, chill) for monkey in data.split('\n\n')]) + + +# Running +game = Game.parse(data, chill=True) +print(game.flag(20)) + +game = Game.parse(data, chill=False) +print(game.flag(10_000))