From 6509fc79ea6c8714c0f607334250f708f469d68f Mon Sep 17 00:00:00 2001 From: Ruediger Ludwig Date: Tue, 7 Feb 2023 21:16:55 +0100 Subject: [PATCH] day11 finished --- Cargo.toml | 4 +- data/day11/example01.txt | 27 +++++ data/day11/input.txt | 55 +++++++++ src/days/day11/mod.rs | 246 +++++++++++++++++++++++++++++++++++++++ src/days/mod.rs | 4 +- 5 files changed, 334 insertions(+), 2 deletions(-) create mode 100644 data/day11/example01.txt create mode 100644 data/day11/input.txt create mode 100644 src/days/day11/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 050a474..0f6fd6a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,5 +7,7 @@ edition = "2021" [dependencies] anyhow = "1.0" itertools = "0.10" -num-traits = "0.2.15" +lazy_static = "1.4" +num-traits = "0.2" +regex = "1.7" thiserror = "1.0" diff --git a/data/day11/example01.txt b/data/day11/example01.txt new file mode 100644 index 0000000..c04eddb --- /dev/null +++ b/data/day11/example01.txt @@ -0,0 +1,27 @@ +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 \ No newline at end of file diff --git a/data/day11/input.txt b/data/day11/input.txt new file mode 100644 index 0000000..4060600 --- /dev/null +++ b/data/day11/input.txt @@ -0,0 +1,55 @@ +Monkey 0: + Starting items: 97, 81, 57, 57, 91, 61 + Operation: new = old * 7 + Test: divisible by 11 + If true: throw to monkey 5 + If false: throw to monkey 6 + +Monkey 1: + Starting items: 88, 62, 68, 90 + Operation: new = old * 17 + Test: divisible by 19 + If true: throw to monkey 4 + If false: throw to monkey 2 + +Monkey 2: + Starting items: 74, 87 + Operation: new = old + 2 + Test: divisible by 5 + If true: throw to monkey 7 + If false: throw to monkey 4 + +Monkey 3: + Starting items: 53, 81, 60, 87, 90, 99, 75 + Operation: new = old + 1 + Test: divisible by 2 + If true: throw to monkey 2 + If false: throw to monkey 1 + +Monkey 4: + Starting items: 57 + Operation: new = old + 6 + Test: divisible by 13 + If true: throw to monkey 7 + If false: throw to monkey 0 + +Monkey 5: + Starting items: 54, 84, 91, 55, 59, 72, 75, 70 + Operation: new = old * old + Test: divisible by 7 + If true: throw to monkey 6 + If false: throw to monkey 3 + +Monkey 6: + Starting items: 95, 79, 79, 68, 78 + Operation: new = old + 3 + Test: divisible by 3 + If true: throw to monkey 1 + If false: throw to monkey 3 + +Monkey 7: + Starting items: 61, 97, 67 + Operation: new = old + 4 + Test: divisible by 17 + If true: throw to monkey 0 + If false: throw to monkey 5 \ No newline at end of file diff --git a/src/days/day11/mod.rs b/src/days/day11/mod.rs new file mode 100644 index 0000000..0f2cd1e --- /dev/null +++ b/src/days/day11/mod.rs @@ -0,0 +1,246 @@ +use super::template::{DayTrait, ResultType}; +use lazy_static::lazy_static; +use regex::Regex; +use std::{iter::zip, num::ParseIntError}; +use thiserror::Error; + +const DAY_NUMBER: usize = 11; + +pub struct Day; + +impl DayTrait for Day { + fn get_day_number(&self) -> usize { + DAY_NUMBER + } + + fn part1(&self, lines: &[String]) -> anyhow::Result { + let troop = Troop::parse(lines)?; + Ok(ResultType::Integer(troop.play(20))) + } + + fn part2(&self, lines: &[String]) -> anyhow::Result { + let troop = Troop::parse(lines)?; + Ok(ResultType::Integer(troop.play_again(10_000))) + } +} + +#[derive(Debug, Error)] +enum MonkeyError { + #[error("Not an Integer")] + NotAnInteger(#[from] ParseIntError), + + #[error("Can't parse line: {0}")] + UnknownLine(String), + + #[error("Did not expect end of Input")] + PrematureEndOfInput, +} + +#[derive(Debug, PartialEq)] +enum Operation { + Plus(i64), + Times(i64), + Squared, +} + +impl Operation { + pub fn parse(line: &str) -> Operation { + match line.split_whitespace().collect::>()[..] { + ["*", "old"] => Operation::Squared, + ["*", value] => Operation::Times(value.parse().unwrap()), + ["+", value] => Operation::Plus(value.parse().unwrap()), + _ => unreachable!(), + } + } + + pub fn calc(&self, old: i64) -> i64 { + match self { + Operation::Plus(value) => old + *value, + Operation::Times(value) => old * value, + Operation::Squared => old.pow(2), + } + } +} + +#[derive(Debug, PartialEq)] +struct Monkey { + number: usize, + items: Vec, + operation: Operation, + divisor: i64, + good_monkey: usize, + bad_monkey: usize, +} + +lazy_static! { + static ref MONKEY: Regex = Regex::new(r"Monkey (\d+)").unwrap(); + static ref STARTING: Regex = Regex::new(r"Starting items: (\d+(?:, \d+)*)").unwrap(); + static ref OP: Regex = Regex::new(r"Operation: new = old ([+*] \d+|\* old)").unwrap(); + static ref TEST: Regex = Regex::new(r"Test: divisible by (\d+)").unwrap(); + static ref NEXT: Regex = Regex::new(r"If (?:true|false): throw to monkey (\d+)").unwrap(); +} + +impl Monkey { + fn parse_line<'a>(re: &Regex, line: &'a str) -> Result<&'a str, MonkeyError> { + let caps = re + .captures(line) + .ok_or(MonkeyError::UnknownLine(line.to_owned()))?; + Ok(caps.get(1).unwrap().as_str()) + } + + pub fn parse_one( + iterator: &mut dyn Iterator, + ) -> Result, MonkeyError> { + if let Some(line) = iterator.next() { + let number: usize = Monkey::parse_line(&MONKEY, line)?.parse()?; + + let line = iterator.next().ok_or(MonkeyError::PrematureEndOfInput)?; + let start = Monkey::parse_line(&STARTING, line)?; + let items = start + .split(", ") + .map(|item| item.parse::()) + .collect::, _>>()?; + + let line = iterator.next().ok_or(MonkeyError::PrematureEndOfInput)?; + let operation = Operation::parse(Monkey::parse_line(&OP, line)?); + + let line = iterator.next().ok_or(MonkeyError::PrematureEndOfInput)?; + let divisor: i64 = Monkey::parse_line(&TEST, line)?.parse()?; + + let line = iterator.next().ok_or(MonkeyError::PrematureEndOfInput)?; + let good_monkey: usize = Monkey::parse_line(&NEXT, line)?.parse()?; + + let line = iterator.next().ok_or(MonkeyError::PrematureEndOfInput)?; + let bad_monkey: usize = Monkey::parse_line(&NEXT, line)?.parse()?; + + iterator.next(); + + Ok(Some(Monkey { + number, + items, + operation, + divisor, + good_monkey, + bad_monkey, + })) + } else { + Ok(None) + } + } + + pub fn process(&self, value: i64) -> i64 { + self.operation.calc(value) + } + + pub fn check(&self, value: i64) -> bool { + value % self.divisor == 0 + } +} + +struct Troop { + monkeys: Vec, +} + +impl Troop { + pub fn parse(lines: &[String]) -> Result { + let mut iter = lines.iter(); + let mut monkeys = Vec::new(); + while let Some(monkey) = Monkey::parse_one(&mut iter)? { + monkeys.push(monkey); + } + Ok(Troop { monkeys }) + } + + fn do_play(&self, rounds: usize, alter: F) -> i64 + where + F: Fn(i64) -> i64, + { + let mut holding = vec![Vec::new(); self.monkeys.len()]; + let mut inspected = vec![0i64; self.monkeys.len()]; + for (holding, monkey) in zip(&mut holding, &self.monkeys) { + holding.extend(&monkey.items); + } + for _ in 0..rounds { + for (pos, monkey) in self.monkeys.iter().enumerate() { + let (good, bad): (Vec<_>, Vec<_>) = holding[pos] + .iter() + .map(|value| alter(monkey.process(*value))) + .partition(|value| monkey.check(*value)); + inspected[pos] += holding[pos].len() as i64; + holding.get_mut(pos).unwrap().clear(); + holding.get_mut(monkey.good_monkey).unwrap().extend(&good); + holding.get_mut(monkey.bad_monkey).unwrap().extend(&bad); + } + } + + inspected.sort_by(|a, b| b.cmp(a)); + inspected.iter().take(2).product() + } + + pub fn play(&self, rounds: usize) -> i64 { + self.do_play(rounds, |v| v / 3) + } + + pub fn play_again(&self, rounds: usize) -> i64 { + let divisor: i64 = self.monkeys.iter().map(|monkey| monkey.divisor).product(); + self.do_play(rounds, |v| v % divisor) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::common::file::read_lines; + use anyhow::Result; + + #[test] + fn test_parse() -> Result<()> { + let day = Day {}; + let lines = read_lines(day.get_day_number(), "example01.txt")?; + let expected = Monkey { + number: 0, + items: vec![79, 98], + operation: Operation::Times(19), + divisor: 23, + good_monkey: 2, + bad_monkey: 3, + }; + let result = Monkey::parse_one(&mut lines.iter())?.unwrap(); + assert_eq!(result, expected); + + Ok(()) + } + + #[test] + fn test_parse_all() -> Result<()> { + let day = Day {}; + let lines = read_lines(day.get_day_number(), "example01.txt")?; + let expected = 4; + let result = Troop::parse(&lines)?; + assert_eq!(result.monkeys.len(), expected); + + Ok(()) + } + + #[test] + fn test_part1() -> Result<()> { + let day = Day {}; + let lines = read_lines(day.get_day_number(), "example01.txt")?; + let expected = ResultType::Integer(10605); + let result = day.part1(&lines)?; + assert_eq!(result, expected); + + Ok(()) + } + + #[test] + fn test_part2() -> Result<()> { + let day = Day {}; + let lines = read_lines(day.get_day_number(), "example01.txt")?; + let expected = ResultType::Integer(2713310158); + let result = day.part2(&lines)?; + assert_eq!(result, expected); + + Ok(()) + } +} diff --git a/src/days/mod.rs b/src/days/mod.rs index a9252e7..7cd8179 100644 --- a/src/days/mod.rs +++ b/src/days/mod.rs @@ -8,6 +8,7 @@ mod day07; mod day08; mod day09; mod day10; +mod day11; mod template; pub use template::DayTrait; @@ -17,7 +18,7 @@ pub mod day_provider { use super::*; use thiserror::Error; - const MAX_DAY: usize = 10; + const MAX_DAY: usize = 11; pub fn get_day(day_num: usize) -> Result, ProviderError> { match day_num { @@ -31,6 +32,7 @@ pub mod day_provider { 8 => Ok(Box::new(day08::Day)), 9 => Ok(Box::new(day09::Day)), 10 => Ok(Box::new(day10::Day)), + 11 => Ok(Box::new(day11::Day)), _ => Err(ProviderError::InvalidNumber(day_num)), } }