advent-of-code/2021/src/bin/day04.rs

130 lines
3.3 KiB
Rust
Raw Normal View History

2021-12-04 12:49:48 +01:00
#![feature(test)]
extern crate test;
use std::collections::HashSet;
use aoc2021::common::*;
use itertools::Itertools;
const DAY: usize = 4;
const BOARD_SIZE: usize = 5;
2021-12-04 13:09:10 +01:00
const NUMBERS_PER_BOARD: usize = BOARD_SIZE * BOARD_SIZE;
2021-12-04 12:49:48 +01:00
type Board = Vec<HashSet<u8>>;
#[derive(Debug, Clone)]
struct BingoGame {
input_numbers: Vec<u8>,
boards: Vec<Board>,
}
impl BingoGame {
fn mark_number(&mut self, n: &u8) {
for board in self.boards.iter_mut() {
for winning_set in board.iter_mut() {
winning_set.remove(n);
}
}
}
fn find_winner(&self) -> Option<&Board> {
self.boards.iter().find(|b| has_won(b))
}
2021-12-04 13:09:10 +01:00
// For assertions in the bechmark
#[cfg(test)]
fn len(&self) -> usize {
self.boards.len()
}
2021-12-04 12:49:48 +01:00
}
fn has_won(board: &Board) -> bool {
board.iter().any(|s| s.is_empty())
}
2021-12-04 13:30:22 +01:00
fn parse_input(raw: &str) -> BingoGame {
2021-12-04 12:49:48 +01:00
let (input_numbers, boards) = raw.split_once("\n\n").unwrap();
let input_numbers = input_numbers.split(',').map(|n| n.parse().unwrap()).collect();
let boards = boards
.split("\n\n")
.map(|b| b.split_ascii_whitespace().map(|n| n.parse().unwrap()).collect())
.map(|v: Vec<u8>| {
2021-12-04 13:09:10 +01:00
debug_assert_eq!(v.len(), NUMBERS_PER_BOARD);
// This seems way too complicated. What am I missing?
(0..NUMBERS_PER_BOARD)
.chain((0..BOARD_SIZE).flat_map(|i| (i..NUMBERS_PER_BOARD).step_by(BOARD_SIZE)))
.map(|i| v[i])
.chunks(BOARD_SIZE)
.into_iter()
.map(|c| c.collect())
.collect()
2021-12-04 12:49:48 +01:00
})
.collect();
BingoGame { input_numbers, boards }
}
fn board_score(board: &Board, current_number: u8) -> usize {
2021-12-04 13:30:22 +01:00
let remainder: usize = board.iter().flatten().unique().map(|&n| n as usize).sum();
2021-12-04 12:49:48 +01:00
remainder * (current_number as usize)
}
2021-12-04 13:30:22 +01:00
fn part1(parsed: &BingoGame) -> usize {
2021-12-04 12:49:48 +01:00
let mut game = parsed.to_owned();
for n in &game.input_numbers.clone() {
game.mark_number(n);
if let Some(board) = game.find_winner() {
return board_score(board, *n);
}
}
unreachable!("Game should have ended at some point")
}
2021-12-04 13:30:22 +01:00
fn part2(parsed: &BingoGame) -> usize {
2021-12-04 12:49:48 +01:00
let mut game = parsed.to_owned();
for n in &game.input_numbers.clone() {
game.mark_number(n);
2021-12-04 13:09:10 +01:00
if game.boards.len() == 1 && has_won(&game.boards[0]) {
return board_score(&game.boards[0], *n);
2021-12-04 12:49:48 +01:00
}
2021-12-04 13:09:10 +01:00
game.boards.retain(|b| !has_won(b));
2021-12-04 12:49:48 +01:00
}
unreachable!("Game should have ended at some point")
}
fn main() {
let input = parse_input(&read_file(DAY));
println!("{input:?}");
println!("Part 1: {}", part1(&input));
println!("Part 2: {}", part2(&input));
}
#[cfg(test)]
mod tests {
use super::*;
use aoc2021::*;
const TEST_INPUT: &str = "7,4,9,5,11,17,23,2,0,14,21,24,10,16,13,6,15,25,12,22,18,20,8,19,3,26,1
22 13 17 11 0
8 2 23 4 24
21 9 14 16 7
6 10 3 18 5
1 12 20 15 19
3 15 0 2 22
9 18 13 17 5
19 8 7 25 23
20 11 10 24 4
14 21 16 12 6
14 21 17 24 4
10 16 15 9 19
18 8 23 26 20
22 11 13 6 5
2 0 12 3 7";
test!(part1() == 4512);
test!(part2() == 1924);
2021-12-04 13:09:10 +01:00
bench!(part1() == 74320);
bench!(part2() == 17884);
bench_input!(BingoGame::len => 100);
2021-12-04 12:49:48 +01:00
}