use std::fmt; use color_eyre::eyre; use eyre::eyre; struct Ratchet where T: Copy, F: Fn(T) -> u8, { value: Option, getter: F, } impl Ratchet where T: Copy, F: Fn(T) -> u8 { fn new(getter: F) -> Self { Ratchet { value: None, getter, } } fn inc(&mut self, other: T) { match self.value { Some(v) if (self.getter)(other) <= (self.getter)(v) => (), _ => self.value = Some(other), }; } fn dec(&mut self, other: T) { match self.value { Some(v) if (self.getter)(other) >= (self.getter)(v) => (), _ => self.value = Some(other), }; } } #[derive(Copy, Clone, Default, Debug)] struct Square { value: u8, draw_time: Option, } #[derive(Copy, Clone, Default, Debug)] struct Board { squares: [[Square; 5]; 5], bingo_time: Option, bingo_value: Option } impl Board { fn from(squares: &[Square]) -> eyre::Result { let mut it = squares.iter(); let mut b = Self::default(); for row in 0..5 { for col in 0..5 { match it.next() { Some(sq) => b.squares[row][col] = *sq, None => return Err(eyre!("Not enough values to populate board")), } } } Ok(b) } fn set_bingo(&mut self) { let mut board_bingo = Ratchet::new(|sq: Square| sq.draw_time.unwrap()); // these unwraps are safe because we will only call inc() or dec() after ensuring there is a value there let mut cols_bingo: [bool; 5] = [true; 5]; let mut cols_last_drawn: Vec> = (0..5) .map(|_i| Ratchet::new(|sq: Square| sq.draw_time.unwrap())) .collect(); // let mut cols_last_drawn: [Ratchet; 5] = [Ratchet::new(|sq: Square| sq.draw_time.unwrap()); 5]; for row in self.squares { let mut row_bingo = true; let mut row_last_drawn = Ratchet::new(|sq: Square| sq.draw_time.unwrap()); for (col, sq) in row.iter().enumerate() { match sq.draw_time { Some(_) => { row_last_drawn.inc(*sq); cols_last_drawn[col].inc(*sq); }, None => { row_bingo = false; cols_bingo[col] = false; break; } } } if row_bingo { let bingo_sq = row_last_drawn.value.unwrap(); // we know there will be a value here if row_bingo is true board_bingo.dec(bingo_sq); // } } for col in 0..5 { if cols_bingo[col] { let bingo_sq = cols_last_drawn[col].value.unwrap(); // similar to above board_bingo.dec(bingo_sq); } } if let Some(sq) = board_bingo.value { self.bingo_time = sq.draw_time; self.bingo_value = Some(sq.value); } } fn score(&self) -> eyre::Result { let bingo_time = match self.bingo_time { Some(t) => t, None => return Err(eyre!("Cannot find the score of a non-winning board")), }; let mut total = 0; for row in self.squares { for sq in row { if sq.draw_time.is_some() && sq.draw_time.unwrap() > bingo_time { total += sq.value as isize; } } } let multiplier = self.bingo_value.unwrap() as isize; Ok(total * multiplier) } } impl fmt::Display for Board { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { for row in self.squares { write!(f, "|")?; for (i, sq) in row.iter().enumerate() { let dt = sq.draw_time.map(|dt| format!("{}", dt)).unwrap_or(String::from("__")); write!(f, "({}: {})", sq.value, dt)?; if i < 4 { write!(f, ", ")?; } } write!(f, "|\n")?; } if self.bingo_time.is_some() { write!(f, "Bingo: ({}: {})", self.bingo_value.unwrap(), self.bingo_time.unwrap()) } else { write!(f, "Bingo: (__: __)") } } } fn load(data: &str) -> eyre::Result> { let mut lines = data.lines().peekable(); let mut draws: [Option; 100] = [None; 100]; // indices are the number drawn, values are the position in which it was drawn for (i, draw) in lines.next().unwrap().split(',').enumerate() { let num = draw.parse::()?; draws[num] = Some(i.try_into().unwrap()); // will panic if there are more than 256 items in this iterator, but there aren't } let mut boards = Vec::new(); let mut squares = Vec::new(); for line in lines { for s in line.split_whitespace() { let value = s.parse::()?; // shadows the loop variable I guess? let sq = Square {value, draw_time: draws[value as usize]}; squares.push(sq); } if squares.len() == 25 { boards.push(Board::from(&squares)?); boards.last_mut().unwrap().set_bingo(); squares.clear(); } } Ok(boards) } fn part1(boards: &[Board]) -> eyre::Result { let winner = boards.iter() .filter(|b| b.bingo_time.is_some()) .min_by_key(|b| b.bingo_time.unwrap()); match winner { Some(w) => Ok(*w), None => Err(eyre!("Could not find a winning board")), } } fn part2(boards: &[Board]) -> eyre::Result { let winner = boards.iter() .filter(|b| b.bingo_time.is_some()) .max_by_key(|b| b.bingo_time.unwrap()); match winner { Some(w) => Ok(*w), None => Err(eyre!("Could not find a winning board")), } } pub fn run(data: &str) -> eyre::Result<(isize, isize)> { let boards = load(data)?; let winner = part1(&boards)?; let loser = part2(&boards)?; Ok((winner.score()?, loser.score()?)) }