Solve day 4!
This commit is contained in:
parent
a25d8733ab
commit
8f62929a85
|
@ -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<Board>,
|
||||
pub numbers: Vec<i32>,
|
||||
}
|
||||
|
||||
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<Self, Self::Err> {
|
||||
let numbers = s
|
||||
.split("\n")
|
||||
.next()
|
||||
.ok_or(eyre!("Didn't find bingo numbers on the first line"))?
|
||||
.split(",")
|
||||
.map(str::parse)
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
let boards = s
|
||||
.split("\n\n")
|
||||
.skip(1)
|
||||
.map(str::parse)
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
Ok(Self { boards, numbers })
|
||||
}
|
||||
}
|
|
@ -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<i32>) -> 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<Self, Self::Err> {
|
||||
let numbers = s
|
||||
.replace("\n", " ")
|
||||
.split(" ")
|
||||
.filter(|s| s != &"")
|
||||
.map(str::parse)
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
Ok(Self::new(numbers))
|
||||
}
|
||||
}
|
|
@ -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<i32> {
|
||||
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<i32> {
|
||||
let (winning_board, latest_number) =
|
||||
Bingo::from_str(input)?.play_until_last_win();
|
||||
Ok(winning_board.sum_unmarked() * latest_number)
|
||||
}
|
|
@ -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(())
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue