aboutsummaryrefslogtreecommitdiff
path: root/src/day4.rs
diff options
context:
space:
mode:
authorAndreas Grois <andi@grois.info>2021-12-06 20:12:47 +0100
committerAndreas Grois <andi@grois.info>2021-12-06 20:53:08 +0100
commit91acf3a03cd8a73cc25c9a7fd652f62f887936b6 (patch)
tree4918cd5121e4d3873a2c562bbb90b8b84b6cad38 /src/day4.rs
parent66248e33e20868c4cc3d6872f1550c0bf9522a94 (diff)
Make Bingo Cards use algebraic sum
Diffstat (limited to 'src/day4.rs')
-rw-r--r--src/day4.rs238
1 files changed, 158 insertions, 80 deletions
diff --git a/src/day4.rs b/src/day4.rs
index edb74eb..4064b34 100644
--- a/src/day4.rs
+++ b/src/day4.rs
@@ -14,7 +14,7 @@ mod bingo_internals {
///This representation was chosen so unfinished games can be continued (not encountered in aoc, but
///I'm doing this as programming training after all...).
#[derive(Debug,Clone)]
- struct BingoCard{
+ struct UnfinishedBingoCard{
rows : [u8;5],
columns : [u8;5],
///the values in the bingo game. Those could be stored in a HashMap, but afaik Rust's
@@ -23,17 +23,18 @@ mod bingo_internals {
contained_numbers : [BingoNumberState;25]
}
- pub struct WonBingoCard<'bingo>{
- card : &'bingo BingoCard
+ #[derive(Debug,Clone,Copy)]
+ struct WonBingoCard{
+ score : usize
}
- impl WonBingoCard<'_> {
- pub fn get_score(&self) -> u32 {
- self.card.get_current_score()
+ impl WonBingoCard {
+ pub fn get_score(&self) -> usize {
+ self.score
}
}
- impl BingoCard {
+ impl UnfinishedBingoCard {
///Creates a new bingo card from a stream of numbers. The stream must yield 25 numbers. To
///allow chaining of card generation, the stream's iterator is returned along with the
///result.
@@ -47,7 +48,7 @@ mod bingo_internals {
match parsed_values.try_into() {
Ok(array) => {
if Self::validate_contained_numbers(&array) {
- (Ok(BingoCard { rows : [0;5], columns : [0;5], contained_numbers : array }), numbers)
+ (Ok(UnfinishedBingoCard { rows : [0;5], columns : [0;5], contained_numbers : array }), numbers)
}
else {
(Err(InvalidBingoFieldError::DuplicateValue),numbers)
@@ -63,17 +64,7 @@ mod bingo_internals {
}
}
}
-
- fn is_won(&self) -> Option<WonBingoCard> {
- if self.rows.iter().chain(self.columns.iter()).any(|&x| x == 5) {
- Some(WonBingoCard { card : self })
- }
- else {
- None
- }
- }
-
- fn cross_number(self, number : u8) -> Self {
+ fn cross_number(self, number : u8) -> BingoCard {
//Sorry for this. Arrays in Rust aren't really functional-style friendly...
//Yes, this is coding using side effects, but the API is how it is...
let mut row_and_column = None;
@@ -85,17 +76,29 @@ mod bingo_internals {
BingoNumberState::Crossed(_) | BingoNumberState::NotCrossed(_) => { number_state }
});
if let Some((row, column)) = row_and_column {
- let mut rows = self.rows;
- rows[row as usize] = self.rows[row as usize]+1;
- let mut columns = self.columns;
- columns[column as usize] = self.columns[column as usize]+1;
- Self{rows, columns, contained_numbers : new_state}
+ let new_row_hit_count = self.rows[row as usize] + 1;
+ let new_col_hit_count = self.columns[column as usize] + 1;
+ if new_col_hit_count == 5 || new_row_hit_count == 5 {
+ BingoCard::Won(WonBingoCard { score : Self::calc_current_score(new_state) })
+ }
+ else {
+ let mut rows = self.rows;
+ rows[row as usize] = new_row_hit_count;
+ let mut columns = self.columns;
+ columns[column as usize] = new_col_hit_count;
+ BingoCard::Unfinished(Self{rows, columns, contained_numbers : new_state})
+ }
}
else {
- Self{rows : self.rows, columns : self.columns, contained_numbers : new_state}
+ BingoCard::Unfinished(Self{rows : self.rows, columns : self.columns, contained_numbers : new_state})
}
}
-
+ fn calc_current_score(numbers : [BingoNumberState;25]) -> usize {
+ numbers.iter().map(|x| match x {
+ BingoNumberState::NotCrossed(BingoNumber{ value, ..}) => { (*value) as usize }
+ BingoNumberState::Crossed(BingoNumber{ .. }) => { 0usize }
+ }).sum()
+ }
fn validate_contained_numbers(numbers : &[BingoNumberState;25]) -> bool {
let bare_values = numbers.iter().map(|x| match x {
BingoNumberState::Crossed(BingoNumber{ value, ..}) => {value}
@@ -105,12 +108,33 @@ mod bingo_internals {
prev && bare_values.clone().skip(index+1).fold(true, |prev, other_number| prev && other_number != number)
})
}
+ }
- fn get_current_score(&self) -> u32 {
- self.contained_numbers.iter().map(|x| match x {
- BingoNumberState::NotCrossed(BingoNumber{ value, ..}) => { (*value) as u32 }
- BingoNumberState::Crossed(BingoNumber{ .. }) => { 0u32 }
- }).sum()
+ #[derive(Debug,Clone)]
+ enum BingoCard {
+ Unfinished(UnfinishedBingoCard),
+ Won(WonBingoCard),
+ }
+
+ impl BingoCard {
+ fn new<'a, T, Q>(numbers : T) -> (Result<Self, InvalidBingoFieldError>, T)
+ where T : Iterator<Item=Q>,
+ Q : Borrow<u8>
+ {
+ let (result, iterator) = UnfinishedBingoCard::new(numbers);
+ (result.map(|x| BingoCard::Unfinished(x)),iterator)
+ }
+ fn get_score(&self) -> Option<usize> {
+ match self {
+ BingoCard::Won(won_bingo_card) => { Some(won_bingo_card.get_score()) }
+ BingoCard::Unfinished(_) => { None }
+ }
+ }
+ fn cross_number(self, value : u8) -> Self {
+ match self {
+ BingoCard::Won(_) => { self }
+ BingoCard::Unfinished(unfinished_card) => { unfinished_card.cross_number(value) }
+ }
}
}
#[derive(Debug)]
@@ -221,8 +245,8 @@ mod bingo_internals {
Self{ cards : self.cards.into_iter().map(|x| x.cross_number(value)).collect() }
}
- pub fn get_winner_cards(&self) -> impl Iterator<Item=WonBingoCard>+Clone+'_ {
- self.cards.iter().filter_map(|x| x.is_won())
+ pub fn get_winner_cards_scores(&self) -> impl Iterator<Item=usize>+Clone+'_ {
+ self.cards.iter().filter_map(|x| x.get_score())
}
}
@@ -240,48 +264,78 @@ mod bingo_internals {
}
#[test]
- fn test_day3_parse_bingo_game() {
+ fn test_day4_parse_bingo_game() {
let game = parse_bingo_game(get_day_4_string_testdata());
assert!(game.is_ok());
let cards = game.unwrap().cards;
assert!(cards.len() == 3);
- assert!((0..3).all(|index| cards[index].contained_numbers.iter().map(|x| match x {
- BingoNumberState::Crossed(_) => { unreachable!() }
- BingoNumberState::NotCrossed( BingoNumber{ value , .. }) => { value }
- }).eq(get_day4_parsed_field_numbers(index).iter())));
- assert_eq!(cards[1].contained_numbers.iter().find_map(|x| {
- if let BingoNumberState::NotCrossed( BingoNumber{value, column, row}) = x{
- if *value == 17{ Some((row, column)) } else { None}
+ assert!((0..3).all(|index| match &cards[index] {
+ BingoCard::Won(_) => { unreachable!() }
+ BingoCard::Unfinished(c) => {
+ c.contained_numbers.iter().map(|x| match x {
+ BingoNumberState::Crossed(_) => { unreachable!() }
+ BingoNumberState::NotCrossed( BingoNumber{ value , .. }) => { value }
+ }).eq(get_day4_parsed_field_numbers(index).iter())
}
- else {
- None
+ }));
+ assert_eq!(match &cards[1] {
+ BingoCard::Won(_) => { unreachable!() }
+ BingoCard::Unfinished(c) => {
+ c.contained_numbers.iter().find_map(|x| {
+ if let BingoNumberState::NotCrossed( BingoNumber{value, column, row}) = x{
+ if *value == 17{ Some((row, column)) } else { None}
+ }
+ else {
+ None
+ }
+ }).unwrap()
}
- }).unwrap(), (&1,&3))
+ }, (&1,&3))
}
#[test]
- fn test_day3_cross_number_in_bingo_card() {
+ fn test_day4_cross_number_in_bingo_card() {
println!("This test relies on test_day3_parse_bingo_game passing. If both fail, fix test_day3_parse_bingo_game first!");
let game = parse_bingo_game(get_day_4_string_testdata());
let mut game = game.unwrap();
- assert!(game.cards[0].contained_numbers.iter().all(|x| match x {
- BingoNumberState::Crossed(_) => {false}
- BingoNumberState::NotCrossed(_) => {true}
- }));
+ assert!(match &game.cards[0] {
+ BingoCard::Won(_) => { unreachable!() }
+ BingoCard::Unfinished(c) => {
+ c.contained_numbers.iter().all(|x| match x {
+ BingoNumberState::Crossed(_) => {false}
+ BingoNumberState::NotCrossed(_) => {true}
+ })
+ }
+ });
let new_card = game.cards.remove(0).cross_number(150);
- assert!(new_card.contained_numbers.iter().all(|x| match x {
- BingoNumberState::Crossed(_) => {false}
- BingoNumberState::NotCrossed(_) => {true}
- }));
+ assert!(match &new_card {
+ BingoCard::Won(_) => { unreachable!() }
+ BingoCard::Unfinished(c) => {
+ c.contained_numbers.iter().all(|x| match x {
+ BingoNumberState::Crossed(_) => {false}
+ BingoNumberState::NotCrossed(_) => {true}
+ })
+ }
+ });
let new_card = new_card.cross_number(24);
- assert_eq!(new_card.contained_numbers.iter().filter(|x| match x {
- BingoNumberState::Crossed(_) => {false}
- BingoNumberState::NotCrossed(_) => {true}
- }).count(), 24);
- let record = new_card.contained_numbers.iter().find(|x| match x {
- BingoNumberState::Crossed(_) => {true}
- BingoNumberState::NotCrossed(_) => {false}
- }).unwrap();
+ assert_eq!(match &new_card {
+ BingoCard::Won(_) => { unreachable!() }
+ BingoCard::Unfinished(c) => {
+ c.contained_numbers.iter().filter(|x| match x {
+ BingoNumberState::Crossed(_) => {false}
+ BingoNumberState::NotCrossed(_) => {true}
+ }).count()
+ }
+ }, 24);
+ let record = match &new_card {
+ BingoCard::Won(_) => { unreachable!() }
+ BingoCard::Unfinished(c) => {
+ c.contained_numbers.iter().find(|x| match x {
+ BingoNumberState::Crossed(_) => {true}
+ BingoNumberState::NotCrossed(_) => {false}
+ }).unwrap()
+ }
+ };
match record {
BingoNumberState::Crossed(BingoNumber{value, row, column}) => {
assert_eq!(value, &24);
@@ -291,19 +345,44 @@ mod bingo_internals {
BingoNumberState::NotCrossed(_) => { unreachable!() }
};
}
+
+ #[test]
+ fn test_day5_win_card() {
+ println!("This test relies on test_day3_parse_bingo_game passing. If both fail, fix test_day3_parse_bingo_game first!");
+ let game = parse_bingo_game(get_day_4_string_testdata()).unwrap();
+ assert_eq!(game.get_winner_cards_scores().count(), 0);
+ let game = game.cross_number(2);
+ assert_eq!(game.get_winner_cards_scores().count(), 0);
+ let game = game.cross_number(17);
+ assert_eq!(game.get_winner_cards_scores().count(), 0);
+ let game = game.cross_number(25);
+ assert_eq!(game.get_winner_cards_scores().count(), 0);
+ let game = game.cross_number(7);
+ assert_eq!(game.get_winner_cards_scores().count(), 0);
+ let game = game.cross_number(24);
+ assert_eq!(game.get_winner_cards_scores().count(), 0);
+ let game = game.cross_number(12);
+ assert_eq!(game.get_winner_cards_scores().sum::<usize>(),237);
+ }
}
}
-
+#[derive(Debug)]
pub struct GameAndInput {
game : BingoGame,
input : Vec<u8>,
}
#[derive(Debug)]
+pub struct WinnerNumberAndScore {
+ number : usize,
+ score : usize,
+}
+
+#[derive(Debug)]
pub enum BingoGameSolutionError {
InsufficientInput { current_game_state : BingoGame },
- Tie { winners : Vec<usize> }
+ Tie { winners : Vec<WinnerNumberAndScore> }
}
impl Display for BingoGameSolutionError {
@@ -313,8 +392,8 @@ impl Display for BingoGameSolutionError {
write!(f, "The game is not completed. The returned error contains the current state, you can continue the game if you have more input")
}
BingoGameSolutionError::Tie{ winners } => {
- write!(f, "There has been a tie. The following players finished the same turn:")?;
- winners.iter().try_for_each(|i| write!(f, " {}", i))
+ write!(f, "There has been a tie. The following players finished the same turn with score:")?;
+ winners.iter().try_for_each(|i| write!(f, " {} {}", i.number, i.score))
}
}
}
@@ -344,24 +423,15 @@ pub fn input_generator<'c>(input : &'c str) -> Result<GameAndInput, BingoGameCre
}
#[aoc(day4, part1)]
-pub fn solve_part1(input : &GameAndInput) -> Result<u32, BingoGameSolutionError> {
+pub fn solve_part1(input : &GameAndInput) -> Result<usize, BingoGameSolutionError> {
use std::ops::ControlFlow as Cf;
let result = input.input.iter().try_fold(input.game.clone(),|game, value| {
let game = game.cross_number(*value);
- let winners = game.get_winner_cards();
- let winners_clone = winners.clone();
- //enum the winners, to see if the last one has index 0
- let last_winner = winners.enumerate().last();
- match last_winner {
- Some((index, card)) => {
- if index == 0 {
- Cf::Break(Ok((*value as u32) * (card.get_score() as u32)))
- }
- else {
- Cf::Break(Err(BingoGameSolutionError::Tie{ winners : winners_clone.enumerate().map(|w| w.0).collect()}))
- }
- }
- None => { drop(last_winner); drop(winners_clone); Cf::Continue(game) }
+ let winners = game.get_winner_cards_scores().enumerate().map(|(number, score)| WinnerNumberAndScore{number, score : score * (*value as usize)}).collect::<Vec<_>>();
+ match winners.len() {
+ 0 => { Cf::Continue(game) }
+ 1 => { Cf::Break(Ok(winners[0].score)) }
+ _ => { Cf::Break(Err(BingoGameSolutionError::Tie{ winners : winners })) }
}
});
match result {
@@ -370,8 +440,10 @@ pub fn solve_part1(input : &GameAndInput) -> Result<u32, BingoGameSolutionError>
}
}
+
#[cfg(test)]
pub mod tests{
+ use super::*;
pub fn get_day_4_string_testdata() -> &'static str {
r#"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
@@ -405,4 +477,10 @@ r#"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
[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],][index]
}
+
+ #[test]
+ pub fn test_day4_solve_part1() {
+ let testdata = input_generator(get_day_4_string_testdata()).unwrap();
+ assert_eq!(solve_part1(&testdata).unwrap(), 4512)
+ }
}