use std::io::Read; use nom::bytes::complete::tag; use nom::character::complete::multispace1; use nom::multi::many1; use nom::multi::separated_list1; use nom::sequence::preceded; use nom::sequence::tuple; use nom::IResult; use crate::common::read_input; #[derive(Debug)] struct BingoCard([(bool, u8); 25]); impl BingoCard { pub fn cross(&mut self, num: u8) -> Option { self.0 .iter_mut() .enumerate() .find_map(|(pos, (ticked, x))| { if *x == num { *ticked = true; Some(pos) } else { None } }) } pub fn has_won(&self, crossed: usize) -> bool { // Check horizontal lines if self .0 .chunks_exact(5) .nth(crossed / 5) .unwrap() .iter() .all(|&b| b.0) { return true; } // Check vertical lines self.0.iter().skip(crossed % 5).step_by(5).all(|b| b.0) // Diagonals do not count } pub fn remaining(&self) -> u32 { self.0 .iter() .filter_map(|&(ticked, num)| if !ticked { Some(num as u32) } else { None }) .sum() } } fn parse_numbers(input: &[u8]) -> IResult<&[u8], Vec> { use nom::character::complete::u8; separated_list1(tag(","), u8)(input) } fn parse_bingo(mut input: &[u8]) -> IResult<&[u8], BingoCard> { use nom::character::complete::u8; let mut card = [0; 25]; let mut parse_num = preceded(multispace1, u8); // fill doesn't work with preceded for num in &mut card { let result = parse_num(input)?; *num = result.1; input = result.0; } Ok((input, BingoCard(card.map(|x| (false, x))))) } fn parse_input(input: &[u8]) -> IResult<&[u8], (Vec, Vec)> { tuple((parse_numbers, many1(parse_bingo)))(input) } pub fn part1(input: &mut dyn Read) -> String { let (numbers, mut bingo_cards) = read_input(input, parse_input); for number in numbers { for card in &mut bingo_cards { if matches!(card.cross(number), Some(pos) if card.has_won(pos)) { return (number as u32 * card.remaining()).to_string(); } } } panic!("None of the cards won") } pub fn part2(input: &mut dyn Read) -> String { let (numbers, mut bingo_cards) = read_input(input, parse_input); let mut bingo_won = vec![false; bingo_cards.len()]; let mut num_won = 0; let to_win = bingo_cards.len(); for num in numbers { for (won, card) in bingo_won.iter_mut().zip(bingo_cards.iter_mut()) { if *won { continue; } if matches!(card.cross(num), Some(pos) if card.has_won(pos)) { *won = true; num_won += 1; if num_won == to_win { return (num as u32 * card.remaining()).to_string(); } } } } panic!("Not all cards won!") } #[cfg(test)] mod tests { use crate::test_implementation; use super::*; const SAMPLE: &[u8] = include_bytes!("samples/04.txt"); #[test] fn sample_part1() { test_implementation(part1, SAMPLE, 4512) } #[test] fn sample_part2() { test_implementation(part2, SAMPLE, 1924) } }