233 lines
6.7 KiB
C#
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
|
|
} |