use std::fs; use std::fmt; use std::path::Path; struct Bingo { cards: Vec } impl Bingo { fn update(&mut self, draw: u32) -> Vec<(usize,u32)> { self.cards.iter_mut().enumerate().filter_map( |(i,c)| { if let Some(s) = c.mark(draw) { if c.ingame { return Some((i,s)) } } None } ).collect() } fn simulate(&mut self, draws: &[u32]) -> u32 { if draws.len() > 0 { if let Some((_,score)) = self.update(draws[0]).first() { *score } else { self.simulate(&draws[1..]) } } else { unreachable!() } } fn simulate_all(&mut self, draws: &[u32]) -> u32 { if draws.len() > 0 { for (index,score) in self.update(draws[0]) { if self.cards.iter().filter(|c| c.ingame).count() == 1 { return score } self.cards[index].ingame = false; } self.simulate_all(&draws[1..]) } else { unreachable!() } } } #[derive(Clone)] struct BingoCard { ingame: bool, nums: Vec<(u32,bool)> } impl BingoCard { fn mark(&mut self, n: u32) -> Option { if let Some(pos) = self.nums.iter().position(|c| c.0 == n) { self.nums[pos].1 = true; if self.check(pos) { Some(self.score() * n) } else { None } } else { None } } fn check(&self, pos: usize) -> bool { let row = pos / 5; let col = pos % 5; (5*row..5*row+5).all(|c| self.nums[c].1) || (0usize..5).all(|c| self.nums[c*5+col].1) } fn score(&self) -> u32 { self.nums.iter().filter_map(|c| if c.1 { None } else { Some(c.0) }).sum() } } impl fmt::Display for BingoCard { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let str = self.nums.iter().enumerate().map( |(n,c)| format!("{:>2}{}{}", c.0, if c.1 { '*' } else { ' ' }, if (n+1)%5 == 0 { '\n' } else { ' ' }) ).collect::>().join(""); write!(f, "{}", str) } } impl From<&str> for BingoCard { fn from(s: &str) -> Self { let mut nums = Vec::with_capacity(25); s.lines().for_each( |l| l.split_whitespace().for_each( |n| nums.push((n.parse().expect("Malformed input"), false)) ) ); BingoCard { ingame: true, nums } } } /* AOC21 Day 4: https://adventofcode.com/2021/day/4 */ fn main() { let input = Path::new("resources").join("input.txt"); let content = fs::read_to_string(input).expect("Unable to read input file"); let report: Vec<&str> = content.split("\n\n").collect(); let draws: Vec = report[0].split(",").map(|x| x.parse().expect("Malformed input")).collect(); let cards: Vec = report[1..].iter().map(|&c| BingoCard::from(c)).collect(); println!("Ex1: The result is {}", Bingo { cards: cards.clone() }.simulate(&draws)); println!("Ex1: The result is {}", Bingo { cards }.simulate_all(&draws)); } #[cfg(test)] mod tests { use super::*; const INPUT: &str = "7,4,9,5,11,17,23,2,0,14,21,24,10,16,13,6,15,25,12,22,18,20,8,19,3,26,1 22 13 17 11 0 8 2 23 4 24 21 9 14 16 7 6 10 3 18 5 1 12 20 15 19 3 15 0 2 22 9 18 13 17 5 19 8 7 25 23 20 11 10 24 4 14 21 16 12 6 14 21 17 24 4 10 16 15 9 19 18 8 23 26 20 22 11 13 6 5 2 0 12 3 7"; #[test] fn winning_score() { let report: Vec<&str> = INPUT.split("\n\n").collect(); let draws: Vec = report[0].split(",").map(|x| x.parse().expect("Malformed input")).collect(); let cards: Vec = report[1..].iter().map(|&c| BingoCard::from(c)).collect(); assert_eq!(4512, Bingo { cards }.simulate(&draws)) } #[test] fn last_winning_score() { let report: Vec<&str> = INPUT.split("\n\n").collect(); let draws: Vec = report[0].split(",").map(|x| x.parse().expect("Malformed input")).collect(); let cards: Vec = report[1..].iter().map(|&c| BingoCard::from(c)).collect(); assert_eq!(1924, Bingo { cards }.simulate_all(&draws)) } }