From 8f62929a8585c4b153f1b81e65e1ad82ec02ea10 Mon Sep 17 00:00:00 2001 From: Bauke Date: Sat, 4 Dec 2021 14:15:12 +0100 Subject: [PATCH] Solve day 4! --- source/day_04/bingo.rs | 77 ++++++++++++++++++++++++++++++++++++ source/day_04/board.rs | 89 ++++++++++++++++++++++++++++++++++++++++++ source/day_04/mod.rs | 27 +++++++++++++ source/main.rs | 2 + 4 files changed, 195 insertions(+) create mode 100644 source/day_04/bingo.rs create mode 100644 source/day_04/board.rs create mode 100644 source/day_04/mod.rs diff --git a/source/day_04/bingo.rs b/source/day_04/bingo.rs new file mode 100644 index 0000000..abc19a1 --- /dev/null +++ b/source/day_04/bingo.rs @@ -0,0 +1,77 @@ +use std::str::FromStr; + +use color_eyre::eyre::{eyre, Error}; + +use super::board::Board; + +#[derive(Debug)] +pub struct Bingo { + pub boards: Vec, + pub numbers: Vec, +} + +impl Bingo { + /// The game loop for part one. + /// + /// Loops over the bingo numbers until it finds the first board that has won. + pub fn play_until_first_win(mut self) -> (Board, i32) { + for number in self.numbers { + for board in &mut self.boards { + board.mark_number(number); + + if board.check() { + return (board.clone(), number); + } + } + } + + unreachable!() + } + + /// The game loop for part two. + /// + /// Loops over the bingo numbers until only one board is left and then returns + /// that as the winner. + pub fn play_until_last_win(mut self) -> (Board, i32) { + for number in self.numbers { + for board in &mut self.boards { + board.mark_number(number); + board.has_won = board.check(); + } + + if self.boards.len() == 1 { + return (self.boards.first().unwrap().clone(), number); + } + + self.boards = self + .boards + .into_iter() + .filter(|board| !board.has_won) + .collect(); + } + + unreachable!() + } +} + +impl FromStr for Bingo { + type Err = Error; + + fn from_str(s: &str) -> Result { + let numbers = s + .split("\n") + .next() + .ok_or(eyre!("Didn't find bingo numbers on the first line"))? + .split(",") + .map(str::parse) + .collect::>()?; + + let boards = s + .split("\n\n") + .skip(1) + .map(str::parse) + .collect::>()?; + + Ok(Self { boards, numbers }) + } +} diff --git a/source/day_04/board.rs b/source/day_04/board.rs new file mode 100644 index 0000000..c52a1da --- /dev/null +++ b/source/day_04/board.rs @@ -0,0 +1,89 @@ +use std::str::FromStr; + +use color_eyre::eyre::Error; + +/// A matrix of all the indexes that constitute a win. +const WIN_MATRIX: &[&[usize]] = &[ + // Horizontal lines + &[00, 01, 02, 03, 04], + &[05, 06, 07, 08, 09], + &[10, 11, 12, 13, 14], + &[15, 16, 17, 18, 19], + &[20, 21, 22, 23, 24], + // Vertical lines + &[00, 05, 10, 15, 20], + &[01, 06, 11, 16, 21], + &[02, 07, 12, 17, 22], + &[03, 08, 13, 18, 23], + &[04, 09, 14, 19, 24], +]; + +#[derive(Debug, Clone)] +pub struct Board { + pub has_won: bool, + pub state: Vec<(i32, bool)>, +} + +impl Board { + /// Create a new board from a set of numbers. + pub fn new(numbers: Vec) -> Self { + Self { + has_won: false, + state: numbers.into_iter().map(|number| (number, false)).collect(), + } + } + + /// Check whether a board has won. + pub fn check(&self) -> bool { + for indexes in WIN_MATRIX { + if indexes.into_iter().all(|index| { + *self + .state + .get(*index) + .map(|(_, state)| state) + .unwrap_or(&false) + }) { + return true; + } + } + + false + } + + /// Marks a target number as true in the board's state. + pub fn mark_number(&mut self, target: i32) { + if let Some(entry) = self.state.iter_mut().find_map(|(number, state)| { + if number == &target { + Some(state) + } else { + None + } + }) { + *entry = true; + } + } + + /// Returns the sum of all numbers that haven't been marked as true. + pub fn sum_unmarked(self) -> i32 { + self + .state + .into_iter() + .filter_map(|(number, state)| if !state { Some(number) } else { None }) + .sum() + } +} + +impl FromStr for Board { + type Err = Error; + + fn from_str(s: &str) -> Result { + let numbers = s + .replace("\n", " ") + .split(" ") + .filter(|s| s != &"") + .map(str::parse) + .collect::>()?; + + Ok(Self::new(numbers)) + } +} diff --git a/source/day_04/mod.rs b/source/day_04/mod.rs new file mode 100644 index 0000000..734330e --- /dev/null +++ b/source/day_04/mod.rs @@ -0,0 +1,27 @@ +use std::str::FromStr; + +use color_eyre::Result; + +mod bingo; +mod board; + +use bingo::Bingo; + +pub fn solve() -> Result<()> { + let input_data = include_str!("../../data/day_04.txt").trim(); + println!("Day 04 Part 1: {}", part_1(input_data)?); + println!("Day 04 Part 2: {}", part_2(input_data)?); + Ok(()) +} + +fn part_1(input: &str) -> Result { + let (winning_board, latest_number) = + Bingo::from_str(input)?.play_until_first_win(); + Ok(winning_board.sum_unmarked() * latest_number) +} + +fn part_2(input: &str) -> Result { + let (winning_board, latest_number) = + Bingo::from_str(input)?.play_until_last_win(); + Ok(winning_board.sum_unmarked() * latest_number) +} diff --git a/source/main.rs b/source/main.rs index b263835..64f67b6 100644 --- a/source/main.rs +++ b/source/main.rs @@ -1,6 +1,7 @@ mod day_01; mod day_02; mod day_03; +mod day_04; fn main() -> color_eyre::Result<()> { color_eyre::install()?; @@ -9,6 +10,7 @@ fn main() -> color_eyre::Result<()> { day_01::solve()?; day_02::solve()?; day_03::solve()?; + day_04::solve()?; Ok(()) }