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
}