aoc-2021/d04/src/main.rs

200 lines
5.6 KiB
Rust

use std::collections::HashSet;
use std::fmt;
use std::fs;
#[derive(Debug, Default, Clone, Copy)]
struct BingoBoard {
values: [[usize; 5]; 5],
called: [[bool; 5]; 5],
}
impl BingoBoard {
fn build_board(input: &[&str]) -> Self {
let mut values = [[0; 5]; 5];
for (i, l) in input.iter().enumerate() {
for (j, v) in l
.split_whitespace()
.map(|n| {
n.parse::<usize>()
.expect(format!("Could not parse {}", n).as_str())
})
.enumerate()
{
values[i][j] = v;
}
}
BingoBoard {
values: values,
called: [[false; 5]; 5],
}
}
fn mark_board(&mut self, number: usize) {
for (r, row) in self.values.iter().enumerate() {
for (c, value) in row.iter().enumerate() {
if *value == number {
self.called[r][c] = true
}
}
}
}
// does winning things
fn is_winner(&self) -> bool {
// Check rows
if self.called.iter().any(|row| row.iter().all(|sq| *sq)) {
return true;
}
let board_size = self.called.len();
for col in 0..board_size {
if self.called.iter().all(|row| row[col]) {
return true;
}
}
return false;
}
fn sum_unmarked(self) -> usize {
self.values
.iter()
.flatten()
.zip(self.called.iter().flatten())
.map(|pair| {
let (val, called) = pair;
if *called {
0
} else {
*val
}
})
.reduce(|acc, v| acc + v)
.unwrap()
}
}
impl fmt::Display for BingoBoard {
// This trait requires `fmt` with this exact signature.
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// Write strictly the first element into the supplied output
// stream: `f`. Returns `fmt::Result` which indicates whether the
// operation succeeded or failed. Note that `write!` uses syntax which
// is very similar to `println!`.
let message = self
.values
.iter()
.zip(self.called.iter())
.map(|x| {
let (val_row, called_row) = x;
val_row
.iter()
.zip(called_row.iter())
.map(|v| {
let (val, called) = v;
let result: String;
if *called {
result = format!("[{}]", val);
} else {
result = format!("{}", val);
}
format!("{: >5}", result)
})
.collect::<Vec<String>>()
.join("")
})
.collect::<Vec<String>>()
.join("\n");
write!(f, "{}", message)
}
}
fn part1() {
let file_contents = fs::read_to_string("input.txt").expect("No input found");
let mut lines = file_contents.lines();
let numbers: Vec<usize> = lines
.next()
.expect("No line found in input")
.split(",")
.map(|n| n.parse().expect(format!("Unknown number: {}", n).as_str()))
.collect();
let board_input: Vec<&str> = lines.filter(|l| l != &"").collect();
let mut boards: Vec<BingoBoard> = board_input
.chunks(5)
.map(|c| BingoBoard::build_board(c))
.collect();
let mut score = 0;
for (turn, num) in numbers.iter().enumerate() {
println!("Turn {}: Called {}", turn, num);
for (board_num, board) in boards.iter_mut().enumerate() {
board.mark_board(*num);
println!("Board {}\n{}\n", board_num, board);
if board.is_winner() {
println!("Winner!");
score = board.sum_unmarked() * num;
break;
}
}
if score != 0 {
println!("Final score is {}", score);
break;
}
}
}
fn part2() {
let file_contents = fs::read_to_string("input.txt").expect("No input found");
let mut lines = file_contents.lines();
let numbers: Vec<usize> = lines
.next()
.expect("No line found in input")
.split(",")
.map(|n| n.parse().expect(format!("Unknown number: {}", n).as_str()))
.collect();
let board_input: Vec<&str> = lines.filter(|l| l != &"").collect();
let mut boards: Vec<BingoBoard> = board_input
.chunks(5)
.map(|c| BingoBoard::build_board(c))
.collect();
let mut score = 0;
let mut winners = HashSet::new();
for (turn, num) in numbers.iter().enumerate() {
println!("Turn {}: Called {}", turn, num);
for (board_num, board) in boards.iter_mut().enumerate() {
// Skip existing winners
if winners.contains(&board_num) {
continue;
}
board.mark_board(*num);
println!("Board {}\n{}\n", board_num, board);
if board.is_winner() {
score = board.sum_unmarked() * num;
println!("Winner! Score {}", score);
winners.insert(board_num);
}
}
if winners.len() == boards.len() {
break;
}
}
if score != 0 {
println!("Final score is {}", score);
}
}
fn main() {
part1();
part2();
}