use std::{collections::LinkedList, io::{stdin, Read}};
fn main() {
let mut stdin = stdin();
let mut buf = String::new();
stdin.read_to_string(&mut buf).unwrap();
let mon = buf.split("\n\n").collect::<Vec<&str>>();
let mut monkeys = mon.iter().map(|n| Monkey::parse(n)).collect::<Vec<Monkey>>();
let needed_mod = monkeys.iter().map(|m| m.test).reduce(|a, b| a*b).unwrap(); // part 2
// let rounds = 20; // part 1
let rounds = 10000;
for i in 0..rounds {
// Monkey::round(&mut monkeys, 0); // part 1. the modulus argument isn't needed in part 1 but i don't want to write the parse function twice
Monkey::round(&mut monkeys, needed_mod); // part 2
}
let mut exams = monkeys.iter().map(|m| m.exams).collect::<Vec<u128>>();
exams.sort_by(|a, b| b.cmp(a));
println!("{:?}", exams[0] * exams[1]);
}
// data for each monkey
struct Monkey {
items: LinkedList<u128>,
oper: Operation,
test: u128,
t: usize,
f: usize,
exams: u128,
}
impl Monkey {
// get monkey from string
fn parse(s: &str) -> Self {
let lines: Vec<&str> = s.split("\n").collect();
let st = &lines[1][18..];
let op = &lines[2][23..];
let ts = lines[3][21..].parse::<u128>().unwrap();
let tr = lines[4][29..].parse::<usize>().unwrap();
let fa = lines[5][30..].parse::<usize>().unwrap();
Monkey {
items: st.split(", ").map(|x| x.parse::<u128>().unwrap()).collect(),
oper: Operation {
ation: op.chars().next().unwrap() == '*',
and: match op[2..].parse::<u128>() {
Ok(x) => Operand::N(x),
Err(_) => Operand::Old
}
},
test: ts,
t: tr,
f: fa,
exams: 0
}
}
// simulate a round of monkeying
fn round(monkeys: &mut Vec<Monkey>, modulus: u128) {
for i in 0..monkeys.len() {
while !monkeys[i].items.is_empty() {
let item = monkeys[i].items.pop_front().unwrap();
monkeys[i].exams += 1;
// let new = monkeys[i].oper.apply(item) / 3; // part 1
let new = monkeys[i].oper.apply(item) % modulus; // part 2
let (t, f) = (monkeys[i].t, monkeys[i].f);
if new % monkeys[i].test == 0 {
monkeys[t].items.push_back(new);
} else {
monkeys[f].items.push_back(new);
}
}
}
}
}
// overengineered way of storing the operators
struct Operation {
ation: bool,
and: Operand,
}
impl Operation {
fn apply(&self, n: u128) -> u128 {
let a = match self.and {
Operand::N(x) => x,
Operand::Old => n
};
if self.ation { n * a } else { n + a }
}
}
enum Operand {
N(u128),
Old
}