Files
AdventOfCode/Days/Day11.cs

233 lines
6.7 KiB
C#

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<long> _items;
private readonly Func<long, long> _operation;
private readonly long _testDivider;
private readonly int _testFailMonkey;
private readonly int _testSuccessMonkey;
public Monkey(int number, IEnumerable<long> items, Func<long, long> operation, long testDivider, int testFailMonkey, int testSuccessMonkey)
{
Number = number;
_operation = operation;
_testDivider = testDivider;
_testFailMonkey = testFailMonkey;
_testSuccessMonkey = testSuccessMonkey;
InspectionCount = 0;
_items = new Queue<long>(items);
}
public void AddItem(long item)
{
_items.Enqueue(item);
}
public bool ThrowItem(IImmutableList<Monkey> 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<Monkey> ParseMonkeys()
{
var monkeys = new List<Monkey>();
// 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<long, long> 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
}