Exercise from advent of code (day 11)

You can find the exercise on: https://adventofcode.com/2022/day/11

This example can best been run locally on your machine, you'll need two files:

  • main.rs
  • input.txt

Solve the error in the rust code.

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
use std::collections::{BTreeMap, HashMap};
use std::fs::File;
use std::io;
use std::io::BufRead;
use std::num::ParseIntError;
use std::path::Path;

#[derive(Debug, Copy, Clone)]
enum Operation {
    Add(i32),
    Multiply(i32),
    AddSelf(),
    MultiplySelf(),
}

impl Operation {
    fn apply(&self, worry_level: i32) -> i32 {
        match self {
            Operation::Add(x) => x + worry_level,
            Operation::Multiply(x) => x * worry_level,
            Operation::AddSelf() => worry_level + worry_level,
            Operation::MultiplySelf() => worry_level * worry_level,
        }
    }
}

#[derive(Debug, Copy, Clone)]
struct Test {
    divisible: i32,
    next_monkey_when_true: char,
    next_monkey_when_false: char,
}

impl Test {
    fn test(&self, worry_level: i32) -> char {
        if worry_level % self.divisible == 0 {
            self.next_monkey_when_true
        } else {
            self.next_monkey_when_false
        }
    }
}

#[derive(Debug, Clone)]
struct Monkey {
    id: char,
    items: Vec<i32>,
    operation: Operation,
    test: Test,
    inspects: i32,
}

impl Monkey {
    fn catch(&self, item: i32) -> Monkey {
        let mut items_to_mutate = self.items.clone();
        items_to_mutate.push(item);
        Monkey {
            id: self.id,
            items: items_to_mutate,
            operation: self.operation,
            test: self.test,
            inspects: self.inspects,
        }
    }
    fn throw(&self) -> (Monkey, char, i32) {
        let (first, tail) = self.items.split_first().unwrap();
        let after_operation = self.operation.apply(*first) / 3;
        let new_monkey = Monkey {
            id: self.id.clone(),
            items: tail.to_vec(),
            operation: self.operation,
            test: self.test,
            inspects: self.inspects + 1,
        };
        (new_monkey, self.test.test(after_operation), after_operation)
    }
}

fn process_items(monkey_id: char, start_play: BTreeMap<char, Monkey>) -> BTreeMap<char, Monkey> {
    let mut end_play = start_play.clone();
    let monkey = start_play.get(&monkey_id).unwrap();

    let (updated_monkey, next_monkey_id, item) = monkey.throw();
    end_play.insert(updated_monkey.id, updated_monkey);

    let monkey_to_receive_item = start_play.get(&next_monkey_id).unwrap();
    let updated_monkey_receive = monkey_to_receive_item.catch(item);
    end_play.insert(updated_monkey_receive.id, updated_monkey_receive);

    end_play
}

fn play_game(start_play: BTreeMap<char, Monkey>) -> BTreeMap<char, Monkey> {
    let mut end_play = start_play.clone();

    for _ in 0..20 {
        for monkey_turn in start_play.keys() {
            let monkey = end_play.get(monkey_turn).unwrap();
            for _ in 0..monkey.items.len() {
                end_play = process_items(*monkey_turn, end_play);
            }
        }
    }

    end_play.clone()
}

fn calculate_score(play: BTreeMap<char, Monkey>) -> i32 {
    let mut scores: Vec<i32> = Vec::new();
    for (_, monkey) in play {
        scores.push(monkey.inspects);
    }
    scores.sort_by(|a, b| b.cmp(a));
    let first = scores.get(0).unwrap();
    let second = scores.get(1).unwrap();

    first * second
}

fn read_from_file(name_file: &str) {
    let mut monkey_id: char = 'q';
    let mut operation = Operation::Add(0);
    let mut test_divisor: i32 = 0;
    let mut test_true_throw: char = 'q';
    let mut test_false_throw: char = 'q';
    let mut items: Vec<i32> = Vec::new();

    let mut all_monkeys: BTreeMap<char, Monkey> = BTreeMap::new();

    if let Ok(lines) = read_lines(name_file) {
        for line in lines {
            if let Ok(input_line) = line {
                if input_line.starts_with("Monkey ") {
                    let possible_monkey_id: Vec<char> = input_line.chars().collect();
                    monkey_id = possible_monkey_id[7].clone();
                }

                let starting_items_header = "  Starting items: ";
                if input_line.starts_with(starting_items_header) {
                    items = input_line.replace(starting_items_header, "")
                        .split(", ")
                        .map(|e| e.parse::<i32>().unwrap())
                        .collect();
                }

                let operation_header = "  Operation: new = ";
                if input_line.starts_with(operation_header) {
                    let number_as_string: String = input_line.chars()
                        .filter(|c| c.is_numeric())
                        .collect();
                    match number_as_string.parse::<i32>() {
                        Ok(number) => {
                            if input_line.contains("*") {
                                operation = Operation::Multiply(number);
                            }
                            if input_line.contains("+") {
                                operation = Operation::Add(number);
                            }
                        }
                        Err(_) => {
                            if input_line.contains("*") {
                                operation = Operation::MultiplySelf();
                            }
                            if input_line.contains("+") {
                                operation = Operation::AddSelf();
                            }
                        }
                    };
                }

                let test_header = "  Test: divisible by ";
                if input_line.starts_with(test_header) {
                    test_divisor = input_line.replace(test_header, "").parse::<i32>().unwrap();
                }

                let test_true_header = "    If true: throw to monkey ";
                if input_line.starts_with(test_true_header) {
                    let number_as_string: String = input_line.chars()
                        .filter(|c| c.is_numeric())
                        .collect();
                    test_true_throw = number_as_string.parse::<char>().unwrap();
                }

                let test_false_header = "    If false: throw to monkey ";
                if input_line.starts_with(test_false_header) {
                    let number_as_string: String = input_line.chars()
                        .filter(|c| c.is_numeric())
                        .collect();
                    test_false_throw = number_as_string.parse::<char>().unwrap();
                }

                // println!("*** {:?}", input_line);
                if input_line.trim().is_empty() {
                    let test = Test {
                        divisible: test_divisor,
                        next_monkey_when_true: test_true_throw,
                        next_monkey_when_false: test_false_throw,
                    };
                    let monkey = Monkey {
                        id: monkey_id,
                        items: items.clone(),
                        inspects: 0,
                        operation,
                        test,
                    };
                    // println!("{:?}", monkey);
                    all_monkeys.insert(monkey_id, monkey);
                }
            }
        }
    }

    let map = play_game(all_monkeys);

    for (c, m) in map {
        println!("{:?} --- {:?} --- {:?} --- {:?}", c, m.id, m.inspects, m.items);
    }

    println!("total score: {}", calculate_score(map));
}

fn main() {
    if let Ok(lines) = read_lines("./input.txt") {
        for line in lines {
            if let Ok(input_line) = line {


                // do something with input_line
            }
        }
    }

    // write a message with result
    let message = "message with result";
    println!("message: {:?}", message);
}


fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
    where P: AsRef<Path>, {
    let file = File::open(filename)?;
    Ok(io::BufReader::new(file).lines())
}


#[cfg(test)]
mod tests {
    // Note this useful idiom: importing names from outer (for mod tests) scope.
    use super::*;


    #[test]
    fn test_add_operation() {
        let ops1 = Operation::Add(4);
        assert_eq!(ops1.apply(5), 9);
    }

    #[test]
    fn test_multiply_operation() {
        let ops1 = Operation::Multiply(4);
        assert_eq!(ops1.apply(5), 20);
    }

    #[test]
    fn test_test_struct_when_false() {
        let test = Test {
            divisible: 11,
            next_monkey_when_true: 'a',
            next_monkey_when_false: 'b',
        };

        assert_eq!(test.test(2), 'b');
    }

    #[test]
    fn test_test_struct_when_true() {
        let test = Test {
            divisible: 11,
            next_monkey_when_true: 'a',
            next_monkey_when_false: 'b',
        };

        assert_eq!(test.test(220), 'a');
    }

    #[test]
    fn test_example() {
        read_from_file("./test-input.txt");
    }

    // #[test]
    // fn test_solution() {
    //     read_from_file("./input.txt");
    // }
}