Add 2022 day 11
This commit is contained in:
parent
59139d65d6
commit
331e08d8de
|
@ -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",
|
||||
]
|
|
@ -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" }
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
Loading…
Reference in New Issue