diff --git a/Cargo.lock b/Cargo.lock index 607b74a..cf808fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,8 +8,15 @@ version = "0.1.0" dependencies = [ "itertools", "nom", + "num", ] +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + [[package]] name = "either" version = "1.8.0" @@ -46,3 +53,79 @@ dependencies = [ "memchr", "minimal-lexical", ] + +[[package]] +name = "num" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] diff --git a/Cargo.toml b/Cargo.toml index be5a346..8310778 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,3 +8,4 @@ edition = "2021" [dependencies] itertools = "0.10.5" nom = "7.1.1" +num = "0.4.0" diff --git a/inputs/day11.txt b/inputs/day11.txt new file mode 100644 index 0000000..79e7808 --- /dev/null +++ b/inputs/day11.txt @@ -0,0 +1,55 @@ +Monkey 0: + Starting items: 56, 52, 58, 96, 70, 75, 72 + Operation: new = old * 17 + Test: divisible by 11 + If true: throw to monkey 2 + If false: throw to monkey 3 + +Monkey 1: + Starting items: 75, 58, 86, 80, 55, 81 + Operation: new = old + 7 + Test: divisible by 3 + If true: throw to monkey 6 + If false: throw to monkey 5 + +Monkey 2: + Starting items: 73, 68, 73, 90 + Operation: new = old * old + Test: divisible by 5 + If true: throw to monkey 1 + If false: throw to monkey 7 + +Monkey 3: + Starting items: 72, 89, 55, 51, 59 + Operation: new = old + 1 + Test: divisible by 7 + If true: throw to monkey 2 + If false: throw to monkey 7 + +Monkey 4: + Starting items: 76, 76, 91 + Operation: new = old * 3 + Test: divisible by 19 + If true: throw to monkey 0 + If false: throw to monkey 3 + +Monkey 5: + Starting items: 88 + Operation: new = old + 4 + Test: divisible by 2 + If true: throw to monkey 6 + If false: throw to monkey 4 + +Monkey 6: + Starting items: 64, 63, 56, 50, 77, 55, 55, 86 + Operation: new = old + 8 + Test: divisible by 13 + If true: throw to monkey 4 + If false: throw to monkey 0 + +Monkey 7: + Starting items: 79, 58 + Operation: new = old + 6 + Test: divisible by 17 + If true: throw to monkey 1 + If false: throw to monkey 5 diff --git a/src/bin/day11_1.rs b/src/bin/day11_1.rs new file mode 100644 index 0000000..0f14028 --- /dev/null +++ b/src/bin/day11_1.rs @@ -0,0 +1,8 @@ +use std::fs; + +use aoc2022::day11::process_part_1; + +fn main() { + let file = fs::read_to_string("./inputs/day11.txt").unwrap(); + println!("{}", process_part_1(&file)); +} diff --git a/src/bin/day11_2.rs b/src/bin/day11_2.rs new file mode 100644 index 0000000..5234d7e --- /dev/null +++ b/src/bin/day11_2.rs @@ -0,0 +1,8 @@ +use std::fs; + +use aoc2022::day11::process_part_2; + +fn main() { + let file = fs::read_to_string("./inputs/day11.txt").unwrap(); + println!("{}", process_part_2(&file)); +} diff --git a/src/day11.rs b/src/day11.rs new file mode 100644 index 0000000..c6ce779 --- /dev/null +++ b/src/day11.rs @@ -0,0 +1,204 @@ +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, + operation: Operation, + test_mod: u64, + target_true: usize, + target_false: usize, + num_inspected: usize, +} + +fn parse(input: &str) -> IResult<&str, Vec> { + 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); + } +} diff --git a/src/lib.rs b/src/lib.rs index 2aeb516..a75caf3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,3 +11,4 @@ pub mod day07; pub mod day08; pub mod day09; pub mod day10; +pub mod day11;