using System.Collections.Immutable; using Spectre.Console; namespace AdventOfCode.Days; public class Monkey { public int Number { get; } public long InspectionCount { get; private set; } private readonly Queue _items; private readonly Func _operation; private readonly long _testDivider; private readonly int _testFailMonkey; private readonly int _testSuccessMonkey; public Monkey(int number, IEnumerable items, Func operation, long testDivider, int testFailMonkey, int testSuccessMonkey) { Number = number; _operation = operation; _testDivider = testDivider; _testFailMonkey = testFailMonkey; _testSuccessMonkey = testSuccessMonkey; InspectionCount = 0; _items = new Queue(items); } public void AddItem(long item) { _items.Enqueue(item); } public bool ThrowItem(IImmutableList monkeys, bool divideWorry = true) { // Return false if there's no item to throw if (_items.Count < 1) { return false; } // Increment inspection count InspectionCount++; // Take next item to throw var item = _items.Dequeue(); // Apply the operation on worry level var newValue = _operation(item); // Divide worry level by 3 before test if (divideWorry) { newValue = newValue / 3; } // Even using this simplification, it's still needed to use longs because // they sometime overflow before being simplified in part 2 newValue = newValue % (2*3*5*7*11*13*17*19); // Test is a success if (newValue % _testDivider == 0) { monkeys[_testSuccessMonkey].AddItem(newValue); } // Test is a fail else { monkeys[_testFailMonkey].AddItem(newValue); } return true; } } public class Day11 : Day { public override int Number => 11; public override string Name => "Monkey in the Middle"; public override void RunPart1(bool display = true) { const int roundsCount = 20; var monkeys = ParseMonkeys(); // Do 20 rounds for (int round = 0; round < roundsCount; round++) { foreach (var monkey in monkeys) { // Throw items while there's some while (monkey.ThrowItem(monkeys)) { } } } var topMonkeys = monkeys.OrderByDescending(m => m.InspectionCount).Take(2).ToList(); if (display) { AnsiConsole.MarkupLine($"[green]Monkey business: [yellow]{topMonkeys[0].InspectionCount * topMonkeys[1].InspectionCount}[/][/]"); } } public override void RunPart2(bool display = true) { const int roundsCount = 10_000; var monkeys = ParseMonkeys(); // Do 10 000 rounds for (int round = 0; round < roundsCount; round++) { foreach (var monkey in monkeys) { // Throw items while there's some while (monkey.ThrowItem(monkeys, false)) { } } } var topMonkeys = monkeys.OrderByDescending(m => m.InspectionCount).Take(2).ToList(); if (display) { AnsiConsole.MarkupLine($"[green]Monkey business: [yellow]{topMonkeys[0].InspectionCount * topMonkeys[1].InspectionCount}[/][/]"); } } private static IImmutableList ParseMonkeys() { var monkeys = new List(); // Split by monkey description foreach (var monkey in Input.ReplaceLineEndings("\n").Split("\n\n")) { var split = monkey.Split('\n'); var numberLine = split[0]; var itemsLine = split[1]; var operationLine = split[2]; var testDividerLine = split[3]; var testSuccessLine = split[4]; var testFailLine = split[5]; // Monkey attributes var number = int.Parse(numberLine[numberLine.LastIndexOf(' ')..^1]); var items = itemsLine[(itemsLine.IndexOf(':') + 2)..].Split(", ").Select(long.Parse); // Read operation operationLine = operationLine[(operationLine.IndexOf('=') + 2)..]; long? rightOperand = operationLine.EndsWith("old") ? null : long.Parse(operationLine.Split('+', '*')[1].Trim()); Func operation = operationLine.Contains('+') switch { true => rightOperand is null ? old => old + old : old => old + (int)rightOperand, false => rightOperand is null ? old => old * old : old => old * (int)rightOperand }; var testDivider = long.Parse(testDividerLine[testDividerLine.LastIndexOf(' ')..]); var testSuccessMonkey = int.Parse(testSuccessLine[testSuccessLine.LastIndexOf(' ')..]); var testFailMonkey = int.Parse(testFailLine[testFailLine.LastIndexOf(' ')..]); monkeys.Add(new Monkey(number, items, operation, testDivider, testFailMonkey, testSuccessMonkey)); } return monkeys.ToImmutableList(); } #region Input public const string Input = """ Monkey 0: Starting items: 89, 95, 92, 64, 87, 68 Operation: new = old * 11 Test: divisible by 2 If true: throw to monkey 7 If false: throw to monkey 4 Monkey 1: Starting items: 87, 67 Operation: new = old + 1 Test: divisible by 13 If true: throw to monkey 3 If false: throw to monkey 6 Monkey 2: Starting items: 95, 79, 92, 82, 60 Operation: new = old + 6 Test: divisible by 3 If true: throw to monkey 1 If false: throw to monkey 6 Monkey 3: Starting items: 67, 97, 56 Operation: new = old * old Test: divisible by 17 If true: throw to monkey 7 If false: throw to monkey 0 Monkey 4: Starting items: 80, 68, 87, 94, 61, 59, 50, 68 Operation: new = old * 7 Test: divisible by 19 If true: throw to monkey 5 If false: throw to monkey 2 Monkey 5: Starting items: 73, 51, 76, 59 Operation: new = old + 8 Test: divisible by 7 If true: throw to monkey 2 If false: throw to monkey 1 Monkey 6: Starting items: 92 Operation: new = old + 5 Test: divisible by 11 If true: throw to monkey 3 If false: throw to monkey 0 Monkey 7: Starting items: 99, 76, 78, 76, 79, 90, 89 Operation: new = old + 7 Test: divisible by 5 If true: throw to monkey 4 If false: throw to monkey 5 """; #endregion }