Sean McLemon | Advent of Code

Home | Czech | Blog | GitHub | Advent Of Code | Notes


2022-12-11 - Monkey in the Middle

(original .ipynb)

Day 11 puzzle input is a description of how some monkeys will throw some items around between them depending on some properties I'm not even going to begin to summarise (mine is here). Part 1 involves simulating how the monkeys will behave for 20 "rounds" of this throwing, then finding the two monkeys which handled/"inspected" each item and multiplying these numbers. Part 2 involves doing adjusting part of the calc (there's a point where we divide a "worriedness" number previously, that we now no longer do) and running for 10,000 rounds.

from functools import reduce
from math import lcm

puzzle_input_str = open("./puzzle_input/day11.txt").read()
test_input_str = open("./test_input/day11.txt").read()

operations = {
    "*": lambda x,y: x*y,
    "+": lambda x,y: x+y,
    "-": lambda x,y: x-y,
    "/": lambda x,y: x//y,
}


def parse_monkey_id(line):
    return int(line[len("Monkey "):][:-1])


def parse_starting_items(line):
    return [int(s) for s in line[len("  Starting items: "):].split(", ")]


def parse_operation(line):
    tokens = line[len("  Operation: "):].split(" ")
    func = operations[tokens[-2]]
    rhs = tokens[-1]
    return (func, rhs if not rhs.isnumeric() else int(rhs))


def parse_divisible_by(line):
    tokens = line.split(" ")
    return int(tokens[-1])


def parse_targets(lines):
    true_line, false_line = lines
    true_monkey = true_line.split(" ")[-1]
    false_monkey = false_line.split(" ")[-1]
    return int(true_monkey), int(false_monkey)


def parse_monkey(monkey_str):
    lines = monkey_str.splitlines()
    monkey_id = parse_monkey_id(lines[0])
    starting_items = parse_starting_items(lines[1])
    operation = parse_operation(lines[2])
    divisible_by = parse_divisible_by(lines[3])
    true_target, false_target = parse_targets(lines[4:])
    return monkey_id, starting_items, operation, divisible_by, true_target, false_target


def get_value(v, old):
    return v if type(v) == int else old


def part_one(input_str, worry_divisor=3, rounds=20):
    monkeys = [parse_monkey(monkey_str) for monkey_str in input_str.split("\n\n")]
    monkey_items = []
    monkey_operations = []
    monkey_divisible_by = []
    monkey_targets = []
    monkey_inspection = []
    
    for monkey_id, starting_items, operation, divisible_by, true_target, false_target in monkeys:
        monkey_items.append(starting_items)
        monkey_operations.append(operation)
        monkey_divisible_by.append(divisible_by)
        monkey_targets.append({
            True: true_target,
            False: false_target
        })
        monkey_inspection.append(0)
 
    foo = lcm(*monkey_divisible_by)

    while rounds > 0:
        for m in range(len(monkeys)):
            next_items = []
            func, rhs = monkey_operations[m]
            
            while len(monkey_items[m]) > 0:
                item = monkey_items[m].pop(0)
                item = func(item, get_value(rhs, item)) // worry_divisor
                res = item % monkey_divisible_by[m] == 0
                monkey_items[monkey_targets[m][res]].append(item % foo)
                monkey_inspection[m] += 1
        rounds -= 1    
    
    return reduce(lambda x,y: x*y, list(sorted(monkey_inspection, reverse=True))[:2])
    

assert 10605 == part_one(test_input_str)

print("part one:", part_one(puzzle_input_str))
part one: 110888
def part_two(input_str):
    return part_one(input_str, 1, 10_000)


assert 2713310158 == part_one(test_input_str, 1, 10_000)

print("part two", part_two(puzzle_input_str))
part two 25590400731