diff --git a/src/day_04.rs b/src/day_04.rs new file mode 100644 index 0000000..a1bb871 --- /dev/null +++ b/src/day_04.rs @@ -0,0 +1,195 @@ +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); + } +} diff --git a/src/lib.rs b/src/lib.rs index 6b0f086..7daf843 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,5 +3,6 @@ use aoc_runner_derive::aoc_lib; pub mod day_01; pub mod day_02; pub mod day_03; +pub mod day_04; aoc_lib! { year = 2021 }