Add 2022 day 11

This commit is contained in:
Kienan Stewart 2022-12-11 12:06:13 -05:00
parent 59139d65d6
commit 331e08d8de
5 changed files with 394 additions and 0 deletions

14
2022/11/Cargo.lock generated Normal file
View File

@ -0,0 +1,14 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "common"
version = "0.1.0"
[[package]]
name = "day11"
version = "0.1.0"
dependencies = [
"common",
]

9
2022/11/Cargo.toml Normal file
View File

@ -0,0 +1,9 @@
[package]
name = "day11"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
common = { path = "../common" }

55
2022/11/input Normal file
View File

@ -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

289
2022/11/src/main.rs Normal file
View File

@ -0,0 +1,289 @@
use std::str::FromStr;
fn main() {
let input_file = common::parse_args_input_file(&mut std::env::args());
let contents = std::fs::read_to_string(input_file).expect("Failed to read input file");
let mut monkeys = Monkey::parse_monkeys(&contents);
let mut rounds = 20;
while rounds > 0 {
Monkey::do_round(&mut monkeys, 3, None);
rounds -= 1;
}
let mut inspection_counts = std::vec::Vec::<u32>::new();
for (index, value) in monkeys.iter().enumerate() {
println!("Monkey {} inspected {} items", index, value.inspection_count);
inspection_counts.push(value.inspection_count);
}
inspection_counts.sort();
println!("[PART 1] Monkey business value: {}", inspection_counts[inspection_counts.len()-1] * inspection_counts[inspection_counts.len()-2]);
monkeys = Monkey::parse_monkeys(&contents);
// To keep numbers manageable, congruence is used. A modulo needs to be picked,
// During each round, items are tested against the test_divisor of the monkey.
// (X/z) ?≡ (Y/z) (mod N) is not always congruent in (mod N) space unless gcd(z, N) = 1
// @see https://www.youtube.com/watch?v=_ge1zGADmWY
//
// In order to satisfy gcd(z, N) = 1 for all z where z is the test_divisor of the monkey,
// then N should be the least common multiple (LCM) of all z.
// I'm not actually sure the LCM(A, B, C) = LCM(LCM(A, B), C), ... so this might be
// working for the wrong reason (eg., the modulo created ends up being a simple multiple
// of all the test_divisors in the problem). It may also only be working since the test_divisors
// in the input are all already prime.
//
let mut modulo = 1;
for m in monkeys.iter() {
modulo = modulo * (m.test_divisor / gcd(modulo, m.test_divisor));
}
println!("Modulo: {}", modulo);
rounds = 10000;
while rounds > 0 {
println!("Round {}", 10000 - rounds);
Monkey::do_round(&mut monkeys, 1, Some(modulo));
rounds -= 1;
}
inspection_counts.clear();
for (index, value) in monkeys.iter().enumerate() {
println!("Monkey {} inspected {} items", index, value.inspection_count);
inspection_counts.push(value.inspection_count);
}
inspection_counts.sort();
println!("[PART 2] (Using mod {}) Monkey business value: {}", modulo, inspection_counts[inspection_counts.len()-1] as f64 * inspection_counts[inspection_counts.len()-2] as f64);
}
// Using binary GCD: https://en.wikipedia.org/wiki/Binary_GCD_algorithm
// Retrieved on 2022-12-11, modified to use i64 instead (but not supporting negative u, v).
pub fn gcd(mut u: i64, mut v: i64) -> i64 {
use std::cmp::min;
use std::mem::swap;
assert!(u >= 0);
assert!(v >= 0);
// Base cases: gcd(n, 0) = gcd(0, n) = n
if u == 0 {
return v;
} else if v == 0 {
return u;
}
// Using identities 2 and 3:
// gcd(2ⁱ u, 2ʲ v) = 2ᵏ gcd(u, v) with u, v odd and k = min(i, j)
// 2ᵏ is the greatest power of two that divides both u and v
let i = u.trailing_zeros(); u >>= i;
let j = v.trailing_zeros(); v >>= j;
let k = min(i, j);
loop {
// u and v are odd at the start of the loop
//debug_assert!(u % 2 == 1, "u = {} is even", u);
//debug_assert!(v % 2 == 1, "v = {} is even", v);
// Swap if necessary so u <= v
if u > v {
swap(&mut u, &mut v);
}
// u and v are still both odd after (potentially) swapping
// Using identity 4 (gcd(u, v) = gcd(|v-u|, min(u, v))
v -= u;
// v is now even, but u is unchanged (and odd)
// Identity 1: gcd(u, 0) = u
// The shift by k is necessary to add back the 2ᵏ factor that was removed before the loop
if v == 0 {
return u << k;
}
// Identity 3: gcd(u, 2ʲ v) = gcd(u, v) (u is known to be odd)
v >>= v.trailing_zeros();
// v is now odd again
}
}
#[derive(Debug)]
struct Monkey {
items: std::vec::Vec<i64>,
update: String,
test_divisor: i64,
success_target: usize,
failure_target: usize,
inspection_count: u32,
}
impl Monkey {
fn operation(&self, old: i64, modulo: Option<i64>) -> i64 {
let words: std::vec::Vec<&str> = self.update.split(' ').collect();
let mut left = if words[0].eq("old") {
old
} else {
i64::from_str(words[0]).unwrap()
};
if modulo.is_some() && left >= modulo.unwrap() {
left %= modulo.unwrap();
}
let mut right = if words[2].eq("old") {
old
} else {
i64::from_str(words[2]).unwrap()
};
if modulo.is_some() && right >= modulo.unwrap() {
right %= modulo.unwrap();
}
let new = match words[1] {
"+" => {
let n = left + right;
if modulo.is_some() {
n % modulo.unwrap()
}
else {
n
}
},
"*" => {
let n = left * right;
if modulo.is_some() {
let x = left as f64;
let c = ((x * (right as f64)) / (modulo.unwrap() as f64)) as i64;
let r = (left * right - c * modulo.unwrap()) % modulo.unwrap();
if r < 0 {
r + modulo.unwrap()
}
else {
r
}
}
else {
n
}
},
_ => {
unreachable!();
}
};
return new;
}
fn parse_monkeys(contents: &String) -> std::vec::Vec<Monkey> {
let mut monkeys = std::vec::Vec::<Monkey>::new();
let mut m = Monkey {
items: std::vec::Vec::<i64>::new(),
update: "".to_string(),
test_divisor: 1,
success_target: 0,
failure_target: 0,
inspection_count: 0,
};
for line in contents.lines() {
if line.eq("") {
monkeys.push(m);
m = Monkey {
items: std::vec::Vec::<i64>::new(),
update: "".to_string(),
test_divisor: 1,
success_target: 0,
failure_target: 0,
inspection_count: 0,
};
continue;
}
if line.trim().starts_with("Monkey ") {
continue;
}
let l = line.trim();
let (_, value) = l.split_once(':').unwrap();
if l.starts_with("Starting items: ") {
for item in value.split(',') {
m.items.push(i64::from_str(item.trim()).unwrap());
}
}
else if l.starts_with("Operation: ") {
let (_, operation) = value.split_once('=').unwrap();
m.update = operation.trim().to_string();
}
else if l.starts_with("Test: ") {
let words: std::vec::Vec<&str> = value.trim().split(' ').collect();
m.test_divisor = i64::from_str(words[2]).unwrap();
}
else if l.starts_with("If true: ") {
let words: std::vec::Vec<&str> = value.trim().split(' ').collect();
m.success_target = usize::from_str(words[3]).unwrap();
}
else if l.starts_with("If false: ") {
let words: std::vec::Vec<&str> = value.trim().split(' ').collect();
m.failure_target = usize::from_str(words[3]).unwrap();
}
else {
unreachable!();
}
}
monkeys.push(m);
return monkeys;
}
fn do_round(monkeys: &mut std::vec::Vec<Monkey>, worry_divisor: i64, modulo: Option<i64>) {
for index in 0..monkeys.len() {
println!("Processing monkey {} ({} items)", index, monkeys[index].items.len());
// Go through each item, starting with front of list
if monkeys[index].items.len() == 0 {
continue;
}
loop {
let mut item = monkeys[index].items.remove(0);
println!(" Inspecting item {}", item);
item = monkeys[index].operation(item, modulo);
println!(" Item updated to {}", item);
if worry_divisor != 1 {
item /= worry_divisor;
println!(" Got bored, item updated to {}", item);
}
monkeys[index].inspection_count += 1;
// Test
println!(" Testing against value {}", monkeys[index].test_divisor);
let target = if item % monkeys[index].test_divisor == 0 {
monkeys[index].success_target
}
else {
monkeys[index].failure_target
};
println!(" Item {} sent to monkey {}", item, target);
monkeys[target].items.push(item);
if monkeys[index].items.len() == 0 {
break;
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse() {
let monkeys = Monkey::parse_monkeys(&std::fs::read_to_string("test_input").unwrap());
assert_eq!(monkeys.len(), 4);
println!("{:?}", monkeys);
}
#[test]
fn operation() {
let mut monkey = Monkey {
items: vec![79, 98],
update: "old * 19".to_string(),
test_divisor: 23,
success_target: 2,
failure_target: 3,
inspection_count: 0,
};
assert_eq!(monkey.operation(79, None), 1501);
monkey.update = "old + 6".to_string();
assert_eq!(monkey.operation(54, None), 60);
monkey.update = "old * old".to_string();
assert_eq!(monkey.operation(5, None), 25);
}
}

27
2022/11/test_input Normal file
View File

@ -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