Files
aoc2022/src/day11.rs
2022-12-11 22:34:12 +01:00

205 lines
5.5 KiB
Rust

use std::collections::VecDeque;
use itertools::Itertools;
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::character::complete;
use nom::character::complete::{newline, space1};
use nom::multi::separated_list1;
use nom::sequence::{delimited, preceded};
use nom::{IResult, Parser};
pub fn process_part_1(input: &str) -> usize {
let mut monkeys = parse(input).unwrap().1;
// Sadly we are doing concurrent array indexing in rust with borrows
for _round in 0..20 {
for monkey_id in 0..monkeys.len() {
while !monkeys[monkey_id].items.is_empty() {
// I'm relying on rust's magic 'figure out when we can drop this to make it work'
// here. This var can be dropped *just* before the last line of the block to make it
// so we aren't borrowing mutably twice.
let monkey = &mut monkeys[monkey_id];
monkey.num_inspected += 1;
let item = monkey.items.pop_front().unwrap();
let item = monkey.operation.execute(item) / 3;
let next_monkey_id = if item % monkey.test_mod == 0 {
monkey.target_true
} else {
monkey.target_false
};
monkeys[next_monkey_id].items.push_back(item);
}
}
}
monkeys
.iter()
.map(|monkey| monkey.num_inspected)
.sorted()
.rev()
.take(2)
.product()
}
pub fn process_part_2(input: &str) -> usize {
let mut monkeys = parse(input).unwrap().1;
// Since we only care for divisibility in tests, we can just modulo every item with
// the LCM of all tests. This allows all tests to check for eligibility without actually
// letting the number grow huge.
let lcm = monkeys
.iter()
.map(|x| x.test_mod)
.reduce(num::integer::lcm)
.unwrap();
for _round in 0..10000 {
for monkey_id in 0..monkeys.len() {
while !monkeys[monkey_id].items.is_empty() {
let monkey = &mut monkeys[monkey_id];
monkey.num_inspected += 1;
let item = monkey.items.pop_front().unwrap();
let item = monkey.operation.execute(item) % lcm;
let next_monkey_id = if item % monkey.test_mod == 0 {
monkey.target_true
} else {
monkey.target_false
};
monkeys[next_monkey_id].items.push_back(item);
}
}
}
monkeys
.iter()
.map(|monkey| monkey.num_inspected)
.sorted()
.rev()
.take(2)
.product()
}
#[derive(Debug)]
enum Operation {
Add(u64),
Multiply(u64),
MultiplySelf,
}
impl Operation {
fn execute(&self, item: u64) -> u64 {
match self {
Operation::Add(x) => item + x,
Operation::Multiply(x) => item * x,
Operation::MultiplySelf => item * item,
}
}
}
#[derive(Debug)]
struct Monkey {
items: VecDeque<u64>,
operation: Operation,
test_mod: u64,
target_true: usize,
target_false: usize,
num_inspected: usize,
}
fn parse(input: &str) -> IResult<&str, Vec<Monkey>> {
separated_list1(newline, parse_monkey)(input)
}
fn parse_monkey(input: &str) -> IResult<&str, Monkey> {
let (input, _id) = delimited(tag("Monkey "), complete::u64, tag(":\n"))(input)?;
let (input, items) = preceded(
space1,
delimited(
tag("Starting items: "),
separated_list1(tag(", "), complete::u64),
newline,
),
)(input)?;
let (input, operation) = delimited(space1, parse_operation, newline)(input)?;
let (input, test_mod) = preceded(
space1,
delimited(tag("Test: divisible by "), complete::u64, newline),
)(input)?;
let (input, target_true) = preceded(
space1,
delimited(tag("If true: throw to monkey "), complete::u64, newline),
)(input)?;
let (input, target_false) = preceded(
space1,
delimited(tag("If false: throw to monkey "), complete::u64, newline),
)(input)?;
Ok((
input,
Monkey {
items: VecDeque::from(items),
operation,
test_mod,
target_true: target_true as usize,
target_false: target_false as usize,
num_inspected: 0,
},
))
}
fn parse_operation(input: &str) -> IResult<&str, Operation> {
let (input, _) = tag("Operation: new = old ")(input)?;
alt((
tag("* old").map(|_| Operation::MultiplySelf),
preceded(tag("+ "), complete::u64).map(Operation::Add),
preceded(tag("* "), complete::u64).map(Operation::Multiply),
))(input)
}
#[cfg(test)]
mod tests {
use super::*;
const INPUT: &str = "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
";
#[test]
fn day1() {
assert_eq!(process_part_1(INPUT), 10605);
}
#[test]
fn day2() {
assert_eq!(process_part_2(INPUT), 2713310158);
}
}