mirror of
https://github.com/bertptrs/adventofcode.git
synced 2025-12-25 12:50:32 +01:00
142 lines
3.4 KiB
Rust
142 lines
3.4 KiB
Rust
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<usize> {
|
|
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<u8>> {
|
|
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<u8>, Vec<BingoCard>)> {
|
|
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)
|
|
}
|
|
}
|