advent_of_code/2020/day11_seating_system/src/lib.rs

161 lines
5 KiB
Rust

use core::fmt::Display;
#[derive(Debug, PartialEq, Eq)]
pub enum ParseError {
InvalidChar(char)
}
impl Display for ParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidChar(c) => write!(f, "Invalid Character encountered: {c}"),
}
}
}
#[derive(PartialEq, Clone)]
enum Seat {
Empty,
Occupied,
Floor,
}
#[derive(Clone)]
struct Ferry {
seats: Vec<Vec<Seat>>,
}
impl TryFrom<&str> for Ferry {
type Error = ParseError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Ok(Self{ seats: value.lines().map(|row|
row.chars()
.map(|c| match c {
'.' => Ok(Seat::Floor),
'L' => Ok(Seat::Empty),
'#' => Ok(Seat::Occupied),
_ => Err(Self::Error::InvalidChar(c)),
}).collect::<Result<Vec<_>, _>>())
.collect::<Result<Vec<_>, _>>()?,
})
}
}
impl Ferry {
fn occupied_neighbours(&self, (x, y): (usize, usize)) -> usize {
(0..=2).map(|dx| (0..=2)
.filter(|&dy| !(dx==1 && dy==1) &&
(1..=self.seats.len()).contains(&(y+dy)) &&
(1..=self.seats[0].len()).contains(&(x+dx)) &&
self.seats[y+dy-1][x+dx-1] == Seat::Occupied).count())
.sum()
}
fn round(&mut self) -> bool {
let mut next = self.seats.clone();
let mut changed = false;
next.iter_mut().enumerate().for_each(|(y, row)|
row.iter_mut().enumerate().for_each(|(x, s)| {
match (&s, self.occupied_neighbours((x, y))) {
(Seat::Empty, 0) => {
*s = Seat::Occupied;
changed = true;
},
(Seat::Occupied, m) if m>3 => {
*s = Seat::Empty;
changed = true;
},
_ => (),
}
}));
if changed {
std::mem::swap(&mut next, &mut self.seats);
true
} else {
false
}
}
fn occupied_in_view(&self, (x, y): (usize, usize)) -> usize {
let mut res = 0;
for dx in -1..=1 {
for dy in -1..=1 {
if dx == 0 && dy == 0 { continue; }
let mut new_x = x as isize + dx;
let mut new_y = y as isize + dy;
while new_x >= 0 && new_y >= 0 && new_y < self.seats.len() as isize && new_x < self.seats[new_y as usize].len() as isize {
match self.seats[new_y as usize][new_x as usize] {
Seat::Floor => {
new_x += dx;
new_y += dy;
},
Seat::Occupied => {
res += 1;
break;
},
Seat::Empty => break,
}
}
}
}
res
}
fn round_v2(&mut self) -> bool {
let mut next = self.seats.clone();
let mut changed = false;
next.iter_mut().enumerate().for_each(|(y, row)|
row.iter_mut().enumerate().for_each(|(x, s)| {
match (&s, self.occupied_in_view((x, y))) {
(Seat::Empty, 0) => {
*s = Seat::Occupied;
changed = true;
},
(Seat::Occupied, m) if m>4 => {
*s = Seat::Empty;
changed = true;
},
_ => (),
}
}));
if changed {
std::mem::swap(&mut next, &mut self.seats);
true
} else {
false
}
}
}
pub fn run(input: &str) -> Result<(usize, usize), ParseError> {
let mut ferry_1 = Ferry::try_from(input)?;
let mut ferry_2 = ferry_1.clone();
while ferry_1.round() {}
while ferry_2.round_v2() {}
let first = ferry_1.seats.iter().map(|row| row.iter().filter(|v| **v == Seat::Occupied).count()).sum();
let second = ferry_2.seats.iter().map(|row| row.iter().filter(|v| **v == Seat::Occupied).count()).sum();
Ok((first, second))
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::read_to_string;
fn read_file(name: &str) -> String {
read_to_string(name).expect(&format!("Unable to read file: {name}")[..]).trim().to_string()
}
#[test]
fn test_sample() {
let sample_input = read_file("tests/sample_input");
assert_eq!(run(&sample_input), Ok((37, 26)));
}
#[test]
fn test_challenge() {
let challenge_input = read_file("tests/challenge_input");
assert_eq!(run(&challenge_input), Ok((2441, 2190)));
}
}