From be518eca39855bcb1bb747f62cc17074702e1a7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Christian=20Gr=C3=BCnhage?= Date: Sat, 4 Dec 2021 22:10:11 +0100 Subject: [PATCH] reimplement parsing in day 4 using yap --- Cargo.lock | 7 ++ Cargo.toml | 1 + src/day_04.rs | 195 ------------------------------------------ src/day_04/bingo.rs | 89 +++++++++++++++++++ src/day_04/mod.rs | 72 ++++++++++++++++ src/day_04/parsing.rs | 90 +++++++++++++++++++ src/lib.rs | 2 + src/parsing.rs | 20 +++++ 8 files changed, 281 insertions(+), 195 deletions(-) delete mode 100644 src/day_04.rs create mode 100644 src/day_04/bingo.rs create mode 100644 src/day_04/mod.rs create mode 100644 src/day_04/parsing.rs create mode 100644 src/parsing.rs diff --git a/Cargo.lock b/Cargo.lock index f6c9aa0..347437b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,6 +9,7 @@ dependencies = [ "aoc-runner", "aoc-runner-derive", "itertools", + "yap", ] [[package]] @@ -129,3 +130,9 @@ name = "unicode-xid" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "yap" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b9d7b5d31406903811da828b29fa5dfd183e37b7c4d4e2968f1fe3b9dde295" diff --git a/Cargo.toml b/Cargo.toml index 5681870..44f0b0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,3 +9,4 @@ edition = "2021" aoc-runner = "0.3.0" aoc-runner-derive = "0.3.0" itertools = "0.10.1" +yap = "0.7.1" diff --git a/src/day_04.rs b/src/day_04.rs deleted file mode 100644 index a1bb871..0000000 --- a/src/day_04.rs +++ /dev/null @@ -1,195 +0,0 @@ -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/day_04/bingo.rs b/src/day_04/bingo.rs new file mode 100644 index 0000000..2044985 --- /dev/null +++ b/src/day_04/bingo.rs @@ -0,0 +1,89 @@ +#[derive(Debug, Clone)] +pub struct Field { + pub number: usize, + pub marked: bool, +} + +#[derive(Debug, Clone)] +pub struct Board { + pub fields: [[Field; 5]; 5], + pub finished: bool, +} + +#[derive(Debug, Clone)] +pub struct Game { + pub boards: Vec, + pub input_numbers: Vec, +} + +impl Board { + pub 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() + } +} + +impl Game { + pub 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 + } +} diff --git a/src/day_04/mod.rs b/src/day_04/mod.rs new file mode 100644 index 0000000..5410d55 --- /dev/null +++ b/src/day_04/mod.rs @@ -0,0 +1,72 @@ +use aoc_runner_derive::{aoc, aoc_generator}; + +mod bingo; +mod parsing; + +pub use bingo::{Board, Field, Game}; + +pub use parsing::parse_game; + +#[aoc_generator(day4)] +pub fn parse_input(input: &str) -> Game { + parse_game(input) +} + +#[aoc(day4, part1)] +pub fn part1(input: &Game) -> usize { + let game = input.clone(); + *game.get_scores(Some(1)).first().unwrap() +} + +#[aoc(day4, part2)] +pub fn part2(input: &Game) -> usize { + let game = input.clone(); + *game.get_scores(None).last().unwrap() +} + +#[cfg(test)] +mod test { + 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); + } + + #[test] + fn parse_example() { + let game = super::parse_game(EXAMPLE_INPUT); + assert!(!game.boards.is_empty()); + assert!(!game.input_numbers.is_empty()); + } +} diff --git a/src/day_04/parsing.rs b/src/day_04/parsing.rs new file mode 100644 index 0000000..7b59ef6 --- /dev/null +++ b/src/day_04/parsing.rs @@ -0,0 +1,90 @@ +use super::bingo::{Board, Field, Game}; +use crate::parsing::{newline, parse_number, space}; +use yap::{IntoTokens, Tokens}; + +pub fn parse_field(tokens: &mut impl Tokens) -> Option { + parse_number(tokens).map(|number| Field { + number, + marked: false, + }) +} + +pub fn parse_field_line(tokens: &mut impl Tokens) -> Option<[Field; 5]> { + tokens + .sep_by(|t| parse_field(t), |t| space(t)) + .collect::>() + .try_into() + .ok() +} + +pub fn parse_field_line_array(tokens: &mut impl Tokens) -> Option<[[Field; 5]; 5]> { + tokens + .sep_by(|t| parse_field_line(t), |t| newline(t)) + .collect::>() + .try_into() + .ok() +} + +pub fn parse_board(tokens: &mut impl Tokens) -> Option { + parse_field_line_array(tokens).map(|fields| Board { + fields, + finished: false, + }) +} + +pub fn parse_boards(tokens: &mut impl Tokens) -> Vec { + tokens + .sep_by(|t| parse_board(t), |t| newline(t) && newline(t)) + .collect() +} + +pub fn parse_input_numbers(tokens: &mut impl Tokens) -> Vec { + tokens + .sep_by(|t| parse_number(t), |t| t.token(',')) + .collect() +} + +pub fn parse_game(input: &str) -> Game { + let mut tokens = input.into_tokens(); + let input_numbers = parse_input_numbers(&mut tokens); + tokens.skip_many(newline); + let boards = parse_boards(&mut tokens); + Game { + input_numbers, + boards, + } +} + +#[cfg(test)] +mod test { + use yap::IntoTokens; + + #[test] + fn parse_input_numbers() { + assert_eq!( + vec![1, 2, 3], + super::parse_input_numbers(&mut "1,2,3".into_tokens()) + ) + } + + #[test] + fn parse_field_line() { + if let Some(line) = super::parse_field_line(&mut " 3 15 0 2 22".into_tokens()) { + assert_eq!(3, line[0].number); + assert_eq!(15, line[1].number); + assert_eq!(0, line[2].number); + assert_eq!(2, line[3].number); + assert_eq!(22, line[4].number); + } + } + + #[test] + fn parse_board() { + const BOARD: &str = " 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"; + assert!(super::parse_board(&mut BOARD.into_tokens()).is_some()); + } +} diff --git a/src/lib.rs b/src/lib.rs index 7daf843..ad13ede 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,7 @@ use aoc_runner_derive::aoc_lib; +pub(crate) mod parsing; + pub mod day_01; pub mod day_02; pub mod day_03; diff --git a/src/parsing.rs b/src/parsing.rs new file mode 100644 index 0000000..92c74ef --- /dev/null +++ b/src/parsing.rs @@ -0,0 +1,20 @@ +use std::str::FromStr; + +use yap::{IntoTokens, Tokens}; +pub fn space(tokens: &mut impl Tokens) -> bool { + tokens.token(' ') +} + +pub fn newline(tokens: &mut impl Tokens) -> bool { + tokens.tokens("\r\n".into_tokens()) || tokens.token('\n') +} + +pub fn parse_number(tokens: &mut impl Tokens) -> Option { + tokens.skip_tokens_while(|t| *t == ' '); + let digits: String = tokens.tokens_while(|c| c.is_digit(10)).collect(); + if digits.len() > 0 { + digits.parse().ok() + } else { + None + } +}