Add day 11.
This commit is contained in:
parent
80465e5e53
commit
c6522de8a2
155
2022/day11.py
Normal file
155
2022/day11.py
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
|
||||||
|
import copy
|
||||||
|
import sys
|
||||||
|
from functools import reduce
|
||||||
|
from typing import Callable
|
||||||
|
|
||||||
|
|
||||||
|
class Monkey:
|
||||||
|
|
||||||
|
id: int
|
||||||
|
items: list[int]
|
||||||
|
worry_fn: Callable[[int], int]
|
||||||
|
test_value: int
|
||||||
|
throw_targets: dict[bool, int]
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
id: int,
|
||||||
|
items: list[int],
|
||||||
|
worry_fn: Callable[[int], int],
|
||||||
|
test_value: int,
|
||||||
|
throw_targets: dict[bool, int],
|
||||||
|
):
|
||||||
|
self.id = id
|
||||||
|
self.items = items
|
||||||
|
self.worry_fn = worry_fn
|
||||||
|
self.test_value = test_value
|
||||||
|
self.throw_targets = throw_targets
|
||||||
|
|
||||||
|
def __eq__(self, o: object) -> bool:
|
||||||
|
if not isinstance(o, Monkey):
|
||||||
|
return False
|
||||||
|
return self.id == o.id
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
return hash(self.id)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_monkey(lines: list[str]) -> Monkey:
|
||||||
|
assert lines[0].startswith("Monkey")
|
||||||
|
|
||||||
|
monkey_id = int(lines[0].split()[-1][:-1])
|
||||||
|
|
||||||
|
# parse items
|
||||||
|
items = [int(r.strip()) for r in lines[1].split(":")[1].split(",")]
|
||||||
|
|
||||||
|
# parse worry
|
||||||
|
worry_fn: Callable[[int], int]
|
||||||
|
worry_s = lines[2].split("new =")[1].strip()
|
||||||
|
operand = worry_s.split()[-1].strip()
|
||||||
|
|
||||||
|
if worry_s.startswith("old *"):
|
||||||
|
if operand == "old":
|
||||||
|
|
||||||
|
def worry_fn(w: int) -> int:
|
||||||
|
return w * w
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
def worry_fn(w: int) -> int:
|
||||||
|
return w * int(operand)
|
||||||
|
|
||||||
|
elif worry_s.startswith("old +"):
|
||||||
|
if operand == "old":
|
||||||
|
|
||||||
|
def worry_fn(w: int) -> int:
|
||||||
|
return w + w
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
def worry_fn(w: int) -> int:
|
||||||
|
return w + int(operand)
|
||||||
|
|
||||||
|
else:
|
||||||
|
assert False, worry_s
|
||||||
|
|
||||||
|
# parse test
|
||||||
|
assert lines[3].split(":")[1].strip().startswith("divisible by")
|
||||||
|
test_value = int(lines[3].split()[-1])
|
||||||
|
|
||||||
|
assert lines[4].strip().startswith("If true")
|
||||||
|
assert lines[5].strip().startswith("If false")
|
||||||
|
throw_targets = {True: int(lines[4].split()[-1]), False: int(lines[5].split()[-1])}
|
||||||
|
|
||||||
|
assert monkey_id not in throw_targets.values()
|
||||||
|
|
||||||
|
return Monkey(monkey_id, items, worry_fn, test_value, throw_targets)
|
||||||
|
|
||||||
|
|
||||||
|
def run(
|
||||||
|
monkeys: list[Monkey], n_rounds: int, me_worry_fn: Callable[[int], int]
|
||||||
|
) -> dict[Monkey, int]:
|
||||||
|
"""
|
||||||
|
The list of monkeys is modified in place.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
monkeys: Initial list of monkeys. The Monkey will be modified in places (their
|
||||||
|
items attributes).
|
||||||
|
n_rounds: Number of rounds to run.
|
||||||
|
me_worry_fn: Worry function to apply after the Monkey operation (e.g., divide
|
||||||
|
by 3 for round 1).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A mapping containing, for each monkey, the number of items inspected.
|
||||||
|
"""
|
||||||
|
inspects = {monkey: 0 for monkey in monkeys}
|
||||||
|
|
||||||
|
for round in range(n_rounds):
|
||||||
|
|
||||||
|
for monkey in monkeys:
|
||||||
|
for item in monkey.items:
|
||||||
|
inspects[monkey] += 1
|
||||||
|
|
||||||
|
# compute the new worry level
|
||||||
|
item = me_worry_fn(monkey.worry_fn(item))
|
||||||
|
|
||||||
|
# find the target
|
||||||
|
target = monkey.throw_targets[item % monkey.test_value == 0]
|
||||||
|
assert target != monkey.id
|
||||||
|
|
||||||
|
monkeys[target].items.append(item)
|
||||||
|
|
||||||
|
# clear after the loop
|
||||||
|
monkey.items.clear()
|
||||||
|
|
||||||
|
return inspects
|
||||||
|
|
||||||
|
|
||||||
|
def monkey_business(inspects: dict[Monkey, int]) -> int:
|
||||||
|
sorted_levels = sorted(inspects.values())
|
||||||
|
return sorted_levels[-2] * sorted_levels[-1]
|
||||||
|
|
||||||
|
|
||||||
|
monkeys = [parse_monkey(block.splitlines()) for block in sys.stdin.read().split("\n\n")]
|
||||||
|
|
||||||
|
# case 1: we simply divide the worry by 3 after applying the monkey worry operation
|
||||||
|
answer_1 = monkey_business(
|
||||||
|
run(copy.deepcopy(monkeys), 20, me_worry_fn=lambda w: w // 3)
|
||||||
|
)
|
||||||
|
print(f"answer 1 is {answer_1}")
|
||||||
|
|
||||||
|
# case 2: to keep reasonable level values, we can use a modulo operation, we need to
|
||||||
|
# use the product of all "divisible by" test so that the test remains valid
|
||||||
|
#
|
||||||
|
# (a + b) % c == ((a % c) + (b % c)) % c --- this would work for a single test value
|
||||||
|
#
|
||||||
|
# (a + b) % c == ((a % d) + (b % d)) % c --- if d is a multiple of c, which is why here
|
||||||
|
# we use the product of all test value
|
||||||
|
#
|
||||||
|
total_test_value = reduce(lambda w, m: w * m.test_value, monkeys, 1)
|
||||||
|
answer_2 = monkey_business(
|
||||||
|
run(copy.deepcopy(monkeys), 10_000, me_worry_fn=lambda w: w % total_test_value)
|
||||||
|
)
|
||||||
|
print(f"answer 2 is {answer_2}")
|
55
2022/inputs/day11.txt
Normal file
55
2022/inputs/day11.txt
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
Monkey 0:
|
||||||
|
Starting items: 54, 53
|
||||||
|
Operation: new = old * 3
|
||||||
|
Test: divisible by 2
|
||||||
|
If true: throw to monkey 2
|
||||||
|
If false: throw to monkey 6
|
||||||
|
|
||||||
|
Monkey 1:
|
||||||
|
Starting items: 95, 88, 75, 81, 91, 67, 65, 84
|
||||||
|
Operation: new = old * 11
|
||||||
|
Test: divisible by 7
|
||||||
|
If true: throw to monkey 3
|
||||||
|
If false: throw to monkey 4
|
||||||
|
|
||||||
|
Monkey 2:
|
||||||
|
Starting items: 76, 81, 50, 93, 96, 81, 83
|
||||||
|
Operation: new = old + 6
|
||||||
|
Test: divisible by 3
|
||||||
|
If true: throw to monkey 5
|
||||||
|
If false: throw to monkey 1
|
||||||
|
|
||||||
|
Monkey 3:
|
||||||
|
Starting items: 83, 85, 85, 63
|
||||||
|
Operation: new = old + 4
|
||||||
|
Test: divisible by 11
|
||||||
|
If true: throw to monkey 7
|
||||||
|
If false: throw to monkey 4
|
||||||
|
|
||||||
|
Monkey 4:
|
||||||
|
Starting items: 85, 52, 64
|
||||||
|
Operation: new = old + 8
|
||||||
|
Test: divisible by 17
|
||||||
|
If true: throw to monkey 0
|
||||||
|
If false: throw to monkey 7
|
||||||
|
|
||||||
|
Monkey 5:
|
||||||
|
Starting items: 57
|
||||||
|
Operation: new = old + 2
|
||||||
|
Test: divisible by 5
|
||||||
|
If true: throw to monkey 1
|
||||||
|
If false: throw to monkey 3
|
||||||
|
|
||||||
|
Monkey 6:
|
||||||
|
Starting items: 60, 95, 76, 66, 91
|
||||||
|
Operation: new = old * old
|
||||||
|
Test: divisible by 13
|
||||||
|
If true: throw to monkey 2
|
||||||
|
If false: throw to monkey 5
|
||||||
|
|
||||||
|
Monkey 7:
|
||||||
|
Starting items: 65, 84, 76, 72, 79, 65
|
||||||
|
Operation: new = old + 5
|
||||||
|
Test: divisible by 19
|
||||||
|
If true: throw to monkey 6
|
||||||
|
If false: throw to monkey 0
|
Loading…
Reference in New Issue
Block a user