const std = @import("std");
const Str = []const u8;
const PATH = "input/day11.txt";
const MONKEYS = 8; // just for asserting
pub fn first(allocator: std.mem.Allocator) !usize {
var arena = std.heap.ArenaAllocator.init(allocator);
defer arena.deinit();
const monkeys = try parseInput(arena.allocator(), @embedFile(PATH));
var round: usize = 0;
while (round < 20) : (round += 1) {
for (monkeys) |*monkey| {
// std.debug.print("Monkey {d}\n", .{idx});
var mItem = monkey.worry.items.len;
while (mItem > 0) : (mItem -= 1) {
const item = monkey.worry.items[mItem - 1];
monkey.inspects += 1;
// std.debug.print(" inspecting {d}\n", .{item});
var op = monkey.operation(item, monkey.operand);
// std.debug.print(" worry level: {d}\n", .{op});
op /= 3;
// std.debug.print(" boring level: {d}\n", .{op});
switch (op % monkey.divisible == 0) {
true => {
// std.debug.print(
// "{d} is divisible with {d}, passing to monkey {d}\n",
// .{ op, monkey.divisible, monkey.throw[0] },
// );
try monkeys[monkey.throw[0]].worry.append(op);
_ = monkey.worry.orderedRemove(mItem - 1);
},
false => {
// std.debug.print(
// "{d} is NOT divisible with {d}, passing to monkey {d}\n",
// .{ op, monkey.divisible, monkey.throw[1] },
// );
try monkeys[monkey.throw[1]].worry.append(op);
_ = monkey.worry.orderedRemove(mItem - 1);
},
}
}
}
// for (monkeys) |m| {
// std.debug.print("{any}\n", .{m.worry.items});
// }
}
var max: [2]usize = .{ 0, 0 };
for (monkeys) |m| {
const idx = std.mem.indexOfMin(usize, &max);
if (m.inspects > max[idx]) max[idx] = m.inspects;
}
return max[0] * max[1];
}
pub fn second(allocator: std.mem.Allocator) !usize {
var arena = std.heap.ArenaAllocator.init(allocator);
defer arena.deinit();
const monkeys = try parseInput(arena.allocator(), @embedFile(PATH));
// We want to know is our number is divisible with all the divisors
// so we can use product of all the divisors.
// Using the least common multiple could be helpful, but not this time.
const prod = blk: {
var p: usize = 1;
for (monkeys) |m| {
p *= m.divisible;
}
break :blk p;
};
var round: usize = 0;
while (round < 10_000) : (round += 1) {
for (monkeys) |*monkey| {
var mItem = monkey.worry.items.len;
while (mItem > 0) : (mItem -= 1) {
const item = monkey.worry.items[mItem - 1];
monkey.inspects += 1;
var op = monkey.operation(item, monkey.operand);
switch (op % monkey.divisible == 0) {
true => {
try monkeys[monkey.throw[0]].worry.append(op % prod);
_ = monkey.worry.orderedRemove(mItem - 1);
},
false => {
try monkeys[monkey.throw[1]].worry.append(op % prod);
_ = monkey.worry.orderedRemove(mItem - 1);
},
}
}
}
}
var max: [2]usize = .{ 0, 0 };
for (monkeys) |m| {
const idx = std.mem.indexOfMin(usize, &max);
if (m.inspects > max[idx]) max[idx] = m.inspects;
}
return max[0] * max[1];
}
test "day11a" {
try std.testing.expectEqual(@as(usize, 55216), try first(std.testing.allocator));
}
test "day11b" {
try std.testing.expectEqual(@as(usize, 12848882750), try second(std.testing.allocator));
}
const Monkey = struct {
worry: std.ArrayList(usize),
operation: *const fn (old: usize, val: ?usize) usize,
operand: ?usize,
divisible: usize,
throw: [2]usize,
inspects: usize,
};
fn parseInput(allocator: std.mem.Allocator, input: Str) ![]Monkey {
var lines = std.mem.split(u8, input, "\n");
var mnkys = std.ArrayList(Monkey).init(allocator);
defer mnkys.deinit();
var m: Monkey = undefined;
var lineNo: isize = 0;
while (lines.next()) |line| : (lineNo += 1) {
if (line.len == 0) {
try mnkys.append(m);
lineNo = -1;
}
// std.debug.print("{d} {s}\n", .{ lineNo, line });
switch (lineNo) {
0 => {
m.worry = std.ArrayList(usize).init(allocator);
m.throw = .{ 0, 0 };
m.inspects = 0;
},
1 => {
var parts = std.mem.tokenize(u8, line, ":");
_ = parts.next(); // drop 'Starting items'
var items = std.mem.tokenize(u8, parts.next().?, ", ");
while (items.next()) |item| {
// std.debug.print("|{s}|\n", .{item});
try m.worry.append(try std.fmt.parseUnsigned(usize, item, 10));
}
},
2 => {
var parts = std.mem.tokenize(u8, line, "=");
_ = parts.next(); // drop 'Operation: new'
var words = std.mem.tokenize(u8, parts.next().?, " ");
_ = words.next(); // old
m.operation = switch (words.next().?[0]) {
'+' => blk: {
break :blk struct {
fn add(old: usize, val: ?usize) usize {
return if (val != null) old + val.? else old + old;
}
}.add;
},
'*' => blk: {
break :blk struct {
fn mul(old: usize, val: ?usize) usize {
return if (val != null) old * val.? else old * old;
}
}.mul;
},
else => unreachable,
};
m.operand = std.fmt.parseUnsigned(usize, words.next().?, 10) catch null;
},
3 => {
const last = std.mem.lastIndexOf(u8, line, " ").?;
m.divisible = try std.fmt.parseUnsigned(usize, line[last + 1 ..], 10);
},
4 => {
m.throw[0] = try std.fmt.parseUnsigned(usize, line[line.len - 1 ..], 10);
std.debug.assert(m.throw[0] < MONKEYS);
},
5 => {
m.throw[1] = try std.fmt.parseUnsigned(usize, line[line.len - 1 ..], 10);
std.debug.assert(m.throw[1] < MONKEYS);
},
-1 => {},
else => unreachable,
}
}
std.debug.assert(mnkys.items.len == MONKEYS);
return mnkys.toOwnedSlice();
}
const test_input =
\\Monkey 0:
\\ Starting items: 79, 98
\\ Operation: new = old * 19
\\ Test: divisible by 23
\\ If true: throw to monkey 2
\\ If false: throw to monkey 3
\\
\\Monkey 1:
\\ Starting items: 54, 65, 75, 74
\\ Operation: new = old + 6
\\ Test: divisible by 19
\\ If true: throw to monkey 2
\\ If false: throw to monkey 0
\\
\\Monkey 2:
\\ Starting items: 79, 60, 97
\\ Operation: new = old * old
\\ Test: divisible by 13
\\ If true: throw to monkey 1
\\ If false: throw to monkey 3
\\
\\Monkey 3:
\\ Starting items: 74
\\ Operation: new = old + 3
\\ Test: divisible by 17
\\ If true: throw to monkey 0
\\ If false: throw to monkey 1
\\
;