use std::convert::TryInto; use std::str::FromStr; use aoc_runner_derive::{aoc, aoc_generator}; #[derive(Debug, Clone)] pub struct Field { number: usize, marked: bool, } #[derive(Debug, Clone)] pub struct BingoBoard { fields: [[Field; 5]; 5], finished: bool, } impl FromStr for BingoBoard { type Err = (); fn from_str(s: &str) -> Result { let fields: [[Field; 5]; 5] = s .lines() .map(|line| line.trim()) .filter(|line| line.len() != 0) .map(|line| -> [Field; 5] { let line: Vec = line .split(' ') .filter(|num| num.len() != 0) .map(|field| { let number = field.parse().unwrap(); Field { number, marked: false, } }) .collect(); line.try_into().unwrap() }) .collect::>() .try_into() .unwrap(); Ok(BingoBoard { fields, finished: false }) } } impl BingoBoard { fn mark(&mut self, num: usize) -> Option { if self.finished == true { return None } for lines in self.fields.iter_mut() { for field in lines.iter_mut() { if field.number == num { field.marked = true; } } } if let Some(score) = self.bingo().map(|pre_score| pre_score * num) { self.finished = true; Some(score) } else { None } } fn bingo(&self) -> Option { let mut completed = false; for i in 0..5 { if (self.fields[i][0].marked && self.fields[i][1].marked && self.fields[i][2].marked && self.fields[i][3].marked && self.fields[i][4].marked) || (self.fields[0][i].marked && self.fields[1][i].marked && self.fields[2][i].marked && self.fields[3][i].marked && self.fields[4][i].marked) { completed = true; } } if completed { Some(self.pre_score()) } else { None } } fn pre_score(&self) -> usize { self.fields .iter() .flat_map(|lines| lines.iter()) .map(|field| if field.marked { 0 } else { field.number }) .sum() } } #[derive(Debug, Clone)] pub struct BingoGame { boards: Vec, input_numbers: Vec, } impl BingoGame { fn get_scores(mut self, max: Option) -> Vec { let mut scores = Vec::new(); for num in self.input_numbers { for board in &mut self.boards { if let Some(score) = board.mark(num) { scores.push(score); if let Some(max) = max { if scores.len() >= max { return scores } } } } } scores } } #[aoc_generator(day4)] pub fn parse_input(input: &str) -> BingoGame { let (input_numbers_str, boards_str) = input.split_once('\n').unwrap(); let input_numbers: Vec = input_numbers_str .split(',') .map(|num| num.parse().unwrap()) .collect(); let boards: Vec = boards_str .split_terminator("\n\n") .map(|board| board.parse().unwrap()) .collect(); BingoGame { boards, input_numbers, } } #[aoc(day4, part1)] pub fn part1(input: &BingoGame) -> usize { let game = input.clone(); *game.get_scores(Some(1)).first().unwrap() } #[aoc(day4, part2)] pub fn part2(input: &BingoGame) -> usize { let game = input.clone(); *game.get_scores(None).last().unwrap() } #[cfg(test)] mod tests { const EXAMPLE_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 "; const RESULT_PART_1: usize = 4512; const RESULT_PART_2: usize = 1924; #[test] fn part1_example() { let result = super::part1(&super::parse_input(EXAMPLE_INPUT)); assert_eq!(result, RESULT_PART_1); } #[test] fn part2_example() { let result = super::part2(&super::parse_input(EXAMPLE_INPUT)); assert_eq!(result, RESULT_PART_2); } }