Cleanup for 2022 day 11: Turned into a lib and introduced parse errors
This commit is contained in:
parent
68802e6959
commit
39d001b6be
3 changed files with 203 additions and 184 deletions
203
2022/day11-monkey_in_the_middle/src/lib.rs
Normal file
203
2022/day11-monkey_in_the_middle/src/lib.rs
Normal file
|
@ -0,0 +1,203 @@
|
|||
use core::fmt::Display;
|
||||
use std::{num::ParseIntError, collections::VecDeque};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum ParseError<'a> {
|
||||
MalformedInput(&'a str),
|
||||
ParseIntError(std::num::ParseIntError),
|
||||
}
|
||||
|
||||
impl From<ParseIntError> for ParseError<'_> {
|
||||
fn from(value: ParseIntError) -> Self {
|
||||
Self::ParseIntError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ParseError<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::MalformedInput(v) => write!(f, "Monkey is malformed: {v}"),
|
||||
Self::ParseIntError(e) => write!(f, "Unable to parse into integer: {e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Copy)]
|
||||
enum WorryLevelBehaviour { DevidedByThree, Constant }
|
||||
|
||||
#[derive(Clone)]
|
||||
enum Operator { Add, Sub, Mul, Div, Pot }
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Operation {
|
||||
operator: Operator,
|
||||
operand: usize,
|
||||
}
|
||||
|
||||
impl Operation {
|
||||
fn from(op_string: &str) -> Self {
|
||||
if op_string == "* old" {
|
||||
Self { operator: Operator::Pot, operand: 2 }
|
||||
} else {
|
||||
let operator = match &op_string[0..1] {
|
||||
"*" => Operator::Mul,
|
||||
"/" => Operator::Div,
|
||||
"+" => Operator::Add,
|
||||
"-" => Operator::Sub,
|
||||
_ => panic!("Unknown Operator in {op_string}"),
|
||||
};
|
||||
let operand = op_string[2..].parse().unwrap();
|
||||
|
||||
Self {
|
||||
operator,
|
||||
operand,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn perform(&self, on: usize) -> usize {
|
||||
match self.operator {
|
||||
Operator::Add => on + self.operand,
|
||||
Operator::Sub => on - self.operand,
|
||||
Operator::Mul => on * self.operand,
|
||||
Operator::Div => on / self.operand,
|
||||
Operator::Pot => on.pow(self.operand as u32),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Monkey {
|
||||
id: usize,
|
||||
items: VecDeque<usize>,
|
||||
operation: Operation,
|
||||
divisibility_test: usize,
|
||||
true_target: usize,
|
||||
false_target: usize,
|
||||
inspected_items: usize,
|
||||
}
|
||||
|
||||
impl <'a> TryFrom<&'a str> for Monkey {
|
||||
type Error = ParseError<'a>;
|
||||
|
||||
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
|
||||
let lines = value.lines().collect::<Vec<&str>>();
|
||||
if lines.len() != 6 ||
|
||||
lines[0].len() < 7 ||
|
||||
lines[1].len() < 18 ||
|
||||
lines[2].len() < 23 ||
|
||||
lines[3].len() < 21 ||
|
||||
lines[4].len() < 29 ||
|
||||
lines[5].len() < 30
|
||||
{
|
||||
return Err(Self::Error::MalformedInput(value));
|
||||
}
|
||||
|
||||
let id = lines[0][7..=7].parse()?;
|
||||
let items = lines[1][18..].split(", ").map(|i| i.parse()).collect::<Result<VecDeque<_>, _>>()?;
|
||||
let operation = Operation::from(&lines[2][23..]);
|
||||
let divisibility_test = lines[3][21..].parse()?;
|
||||
let true_target = lines[4][29..].parse()?;
|
||||
let false_target = lines[5][30..].parse()?;
|
||||
|
||||
Ok(Self {
|
||||
id,
|
||||
items,
|
||||
operation,
|
||||
divisibility_test,
|
||||
true_target,
|
||||
false_target,
|
||||
inspected_items: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Monkey {
|
||||
fn play(&mut self, queues: &mut [VecDeque<usize>], worry_level_behaviour: WorryLevelBehaviour, lcm: usize) {
|
||||
self.items.append(&mut queues[self.id]);
|
||||
while let Some(mut item) = self.items.pop_front() {
|
||||
self.inspected_items += 1;
|
||||
item = (self.operation).perform(item);
|
||||
if worry_level_behaviour == WorryLevelBehaviour::DevidedByThree {
|
||||
item /= 3;
|
||||
} else {
|
||||
item %= lcm;
|
||||
}
|
||||
if item % self.divisibility_test == 0 {
|
||||
queues[self.true_target].push_back(item);
|
||||
} else {
|
||||
queues[self.false_target].push_back(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(input: &str) -> Result<(usize, usize), ParseError> {
|
||||
let mut monkeys_1 = input.split("\n\n").map(Monkey::try_from).collect::<Result<Vec<_>, _>>()?;
|
||||
let mut monkeys_2 = monkeys_1.to_vec();
|
||||
let lcm = monkeys_1.iter().map(|monkey| monkey.divisibility_test).reduce(lcm).expect("Unable to find an lcm");
|
||||
let first = get_inspections(&mut monkeys_1, lcm, 20, WorryLevelBehaviour::DevidedByThree);
|
||||
let second = get_inspections(&mut monkeys_2, lcm, 10_000, WorryLevelBehaviour::Constant);
|
||||
Ok((first, second))
|
||||
}
|
||||
|
||||
fn lcm(first: usize, second: usize) -> usize {
|
||||
first * second / gcd(first, second)
|
||||
}
|
||||
|
||||
fn gcd(first: usize, second: usize) -> usize {
|
||||
let mut max = first;
|
||||
let mut min = second;
|
||||
if min > max {
|
||||
std::mem::swap(&mut max, &mut min);
|
||||
}
|
||||
|
||||
loop {
|
||||
let res = max % min;
|
||||
if res == 0 {
|
||||
return min;
|
||||
}
|
||||
|
||||
max = min;
|
||||
min = res;
|
||||
}
|
||||
}
|
||||
|
||||
fn get_inspections(monkeys: &mut [Monkey], lcm: usize, rounds: usize, behaviour: WorryLevelBehaviour) -> usize {
|
||||
let mut queues: Vec<VecDeque<usize>> = vec![VecDeque::new(); monkeys.len()];
|
||||
|
||||
for _ in 0..rounds {
|
||||
for monkey in monkeys.iter_mut() {
|
||||
monkey.play(&mut queues, behaviour, lcm);
|
||||
}
|
||||
}
|
||||
|
||||
let mut inspections = monkeys.iter()
|
||||
.map(|monkey| monkey.inspected_items)
|
||||
.collect::<Vec<usize>>();
|
||||
inspections.sort_by_key(|i| std::cmp::Reverse(*i));
|
||||
|
||||
inspections[0] * inspections[1]
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs::read_to_string;
|
||||
|
||||
fn read_file(name: &str) -> String {
|
||||
read_to_string(name).expect(&format!("Unable to read file: {name}")[..]).trim().to_string()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sample() {
|
||||
let sample_input = read_file("tests/sample_input");
|
||||
assert_eq!(run(&sample_input), Ok((10605, 2713310158)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_challenge() {
|
||||
let challenge_input = read_file("tests/challenge_input");
|
||||
assert_eq!(run(&challenge_input), Ok((69918, 19573408701)));
|
||||
}
|
||||
}
|
|
@ -1,184 +0,0 @@
|
|||
use std::fs;
|
||||
|
||||
#[derive(PartialEq, Clone, Copy)]
|
||||
enum WorryLevelBehaviour { DevidedByThree, Constant }
|
||||
|
||||
enum Operator { Add, Sub, Mul, Div, Pot }
|
||||
|
||||
struct Operation {
|
||||
operator: Operator,
|
||||
operand: u128,
|
||||
}
|
||||
|
||||
impl Operation {
|
||||
fn from(op_string: &str) -> Self {
|
||||
if op_string == "* old" {
|
||||
Self { operator: Operator::Pot, operand: 2 }
|
||||
} else {
|
||||
let operator = match &op_string[0..1] {
|
||||
"*" => Operator::Mul,
|
||||
"/" => Operator::Div,
|
||||
"+" => Operator::Add,
|
||||
"-" => Operator::Sub,
|
||||
_ => panic!("Unknown Operator in {op_string}"),
|
||||
};
|
||||
let operand = op_string[2..].parse().unwrap();
|
||||
|
||||
Self {
|
||||
operator,
|
||||
operand,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn perform(&self, on: u128) -> u128 {
|
||||
match self.operator {
|
||||
Operator::Add => on + self.operand,
|
||||
Operator::Sub => on - self.operand,
|
||||
Operator::Mul => on * self.operand,
|
||||
Operator::Div => on / self.operand,
|
||||
Operator::Pot => on.pow(self.operand as u32),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Monkey {
|
||||
id: usize,
|
||||
items: Vec<u128>,
|
||||
operation: Operation,
|
||||
divisibility_test: u128,
|
||||
true_target: usize,
|
||||
false_target: usize,
|
||||
inspected_items: u128,
|
||||
}
|
||||
|
||||
impl Monkey {
|
||||
fn from(monkey_string: &str) -> Self {
|
||||
let lines = monkey_string.split('\n').collect::<Vec<&str>>();
|
||||
|
||||
let id = lines[0][7..=7].parse().unwrap();
|
||||
let items = lines[1][18..].split(", ").map(|i| i.parse().unwrap()).collect();
|
||||
let operation = Operation::from(&lines[2][23..]);
|
||||
let divisibility_test = lines[3][21..].parse().unwrap();
|
||||
let true_target = lines[4][29..].parse().unwrap();
|
||||
let false_target = lines[5][30..].parse().unwrap();
|
||||
|
||||
Self {
|
||||
id,
|
||||
items,
|
||||
operation,
|
||||
divisibility_test,
|
||||
true_target,
|
||||
false_target,
|
||||
inspected_items: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn receive(&mut self, queue: &mut Vec<u128>) {
|
||||
self.items.append(queue);
|
||||
queue.clear();
|
||||
}
|
||||
|
||||
fn send(&mut self, item: u128, monkey: usize, queues: &mut [Vec<u128>]) {
|
||||
queues[monkey].push(item);
|
||||
self.items.remove(0);
|
||||
}
|
||||
|
||||
fn play(&mut self, queues: &mut [Vec<u128>], worry_level_behaviour: WorryLevelBehaviour, lcm: u128) {
|
||||
self.receive(&mut queues[self.id]);
|
||||
while !self.items.is_empty() {
|
||||
let mut item = self.items[0];
|
||||
self.inspected_items += 1;
|
||||
item = (self.operation).perform(item);
|
||||
if worry_level_behaviour == WorryLevelBehaviour::DevidedByThree {
|
||||
item /= 3;
|
||||
} else {
|
||||
item %= lcm;
|
||||
}
|
||||
if item % self.divisibility_test == 0 {
|
||||
self.send(item, self.true_target, queues);
|
||||
} else {
|
||||
self.send(item, self.false_target, queues);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read_file(path: &str) -> String {
|
||||
fs::read_to_string(path)
|
||||
.expect("File not Found")
|
||||
}
|
||||
|
||||
fn lcm(first: u128, second: u128) -> u128 {
|
||||
first * second / gcd(first, second)
|
||||
}
|
||||
|
||||
fn gcd(first: u128, second: u128) -> u128 {
|
||||
let mut max = first;
|
||||
let mut min = second;
|
||||
if min > max {
|
||||
std::mem::swap(&mut max, &mut min);
|
||||
}
|
||||
|
||||
loop {
|
||||
let res = max % min;
|
||||
if res == 0 {
|
||||
return min;
|
||||
}
|
||||
|
||||
max = min;
|
||||
min = res;
|
||||
}
|
||||
}
|
||||
|
||||
fn get_inspections(monkey_list: &str, rounds: usize, behaviour: WorryLevelBehaviour) -> u128 {
|
||||
let mut monkeys = Vec::new();
|
||||
let mut queues: Vec<Vec<u128>> = Vec::new();
|
||||
for monkey_str in monkey_list.split("\n\n") {
|
||||
monkeys.push(Monkey::from(monkey_str));
|
||||
queues.push(Vec::new());
|
||||
}
|
||||
|
||||
let lcm = monkeys.iter()
|
||||
.map(|monkey| monkey.divisibility_test)
|
||||
.reduce(lcm)
|
||||
.unwrap();
|
||||
|
||||
for _ in 0..rounds {
|
||||
for monkey in &mut monkeys {
|
||||
monkey.play(&mut queues, behaviour, lcm);
|
||||
}
|
||||
}
|
||||
|
||||
let mut inspections = monkeys.iter()
|
||||
.map(|monkey| monkey.inspected_items)
|
||||
.collect::<Vec<u128>>();
|
||||
inspections.sort();
|
||||
inspections.reverse();
|
||||
|
||||
inspections[0] * inspections[1]
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let monkey_list = read_file("input");
|
||||
|
||||
println!("Before your worries increase, the top monkeys' inspections multiply into {}.", get_inspections(&monkey_list, 20, WorryLevelBehaviour::DevidedByThree));
|
||||
println!("After your worries increase, the top monkeys' inspections multiply into {}.", get_inspections(&monkey_list, 10_000, WorryLevelBehaviour::Constant));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sample_input() {
|
||||
let monkey_list = read_file("tests/sample_input");
|
||||
|
||||
assert_eq!(get_inspections(&monkey_list, 20, WorryLevelBehaviour::DevidedByThree), 10605);
|
||||
assert_eq!(get_inspections(&monkey_list, 10_000, WorryLevelBehaviour::Constant), 2713310158);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn challenge_input() {
|
||||
let monkey_list = read_file("tests/input");
|
||||
|
||||
assert_eq!(get_inspections(&monkey_list, 20, WorryLevelBehaviour::DevidedByThree), 69918);
|
||||
assert_eq!(get_inspections(&monkey_list, 10_000, WorryLevelBehaviour::Constant), 19573408701);
|
||||
}
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue