reimplement parsing in day 4 using yap

This commit is contained in:
Jan Christian Grünhage 2021-12-04 22:10:11 +01:00
parent 0863785c00
commit be518eca39
8 changed files with 281 additions and 195 deletions

7
Cargo.lock generated
View file

@ -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"

View file

@ -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"

View file

@ -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<Self, Self::Err> {
let fields: [[Field; 5]; 5] = s
.lines()
.map(|line| line.trim())
.filter(|line| line.len() != 0)
.map(|line| -> [Field; 5] {
let line: Vec<Field> = line
.split(' ')
.filter(|num| num.len() != 0)
.map(|field| {
let number = field.parse().unwrap();
Field {
number,
marked: false,
}
})
.collect();
line.try_into().unwrap()
})
.collect::<Vec<[Field; 5]>>()
.try_into()
.unwrap();
Ok(BingoBoard { fields, finished: false })
}
}
impl BingoBoard {
fn mark(&mut self, num: usize) -> Option<usize> {
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<usize> {
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<BingoBoard>,
input_numbers: Vec<usize>,
}
impl BingoGame {
fn get_scores(mut self, max: Option<usize>) -> Vec<usize> {
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<usize> = input_numbers_str
.split(',')
.map(|num| num.parse().unwrap())
.collect();
let boards: Vec<BingoBoard> = 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);
}
}

89
src/day_04/bingo.rs Normal file
View file

@ -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<Board>,
pub input_numbers: Vec<usize>,
}
impl Board {
pub fn mark(&mut self, num: usize) -> Option<usize> {
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<usize> {
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<usize>) -> Vec<usize> {
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
}
}

72
src/day_04/mod.rs Normal file
View file

@ -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());
}
}

90
src/day_04/parsing.rs Normal file
View file

@ -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<Item = char>) -> Option<Field> {
parse_number(tokens).map(|number| Field {
number,
marked: false,
})
}
pub fn parse_field_line(tokens: &mut impl Tokens<Item = char>) -> Option<[Field; 5]> {
tokens
.sep_by(|t| parse_field(t), |t| space(t))
.collect::<Vec<Field>>()
.try_into()
.ok()
}
pub fn parse_field_line_array(tokens: &mut impl Tokens<Item = char>) -> Option<[[Field; 5]; 5]> {
tokens
.sep_by(|t| parse_field_line(t), |t| newline(t))
.collect::<Vec<[Field; 5]>>()
.try_into()
.ok()
}
pub fn parse_board(tokens: &mut impl Tokens<Item = char>) -> Option<Board> {
parse_field_line_array(tokens).map(|fields| Board {
fields,
finished: false,
})
}
pub fn parse_boards(tokens: &mut impl Tokens<Item = char>) -> Vec<Board> {
tokens
.sep_by(|t| parse_board(t), |t| newline(t) && newline(t))
.collect()
}
pub fn parse_input_numbers(tokens: &mut impl Tokens<Item = char>) -> Vec<usize> {
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());
}
}

View file

@ -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;

20
src/parsing.rs Normal file
View file

@ -0,0 +1,20 @@
use std::str::FromStr;
use yap::{IntoTokens, Tokens};
pub fn space(tokens: &mut impl Tokens<Item = char>) -> bool {
tokens.token(' ')
}
pub fn newline(tokens: &mut impl Tokens<Item = char>) -> bool {
tokens.tokens("\r\n".into_tokens()) || tokens.token('\n')
}
pub fn parse_number<T: FromStr>(tokens: &mut impl Tokens<Item = char>) -> Option<T> {
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
}
}