2020-12-11 14:37:14 +01:00
|
|
|
use std::fmt::Write;
|
|
|
|
|
2020-12-14 18:08:16 +01:00
|
|
|
use anyhow::{anyhow, Result};
|
2020-12-11 14:37:14 +01:00
|
|
|
|
|
|
|
const INPUT: &str = include_str!("../input/day11.txt");
|
|
|
|
|
2020-12-14 18:08:16 +01:00
|
|
|
pub fn run() -> Result<String> {
|
2020-12-11 14:37:14 +01:00
|
|
|
let mut res = String::with_capacity(128);
|
|
|
|
|
|
|
|
writeln!(res, "part 1: {}", part1(INPUT)?)?;
|
2020-12-11 15:09:10 +01:00
|
|
|
writeln!(res, "part 2: {}", part2(INPUT)?)?;
|
2020-12-11 14:37:14 +01:00
|
|
|
|
|
|
|
Ok(res)
|
|
|
|
}
|
|
|
|
|
2020-12-14 18:08:16 +01:00
|
|
|
fn part1(input: &str) -> Result<usize> {
|
2020-12-11 14:37:14 +01:00
|
|
|
let mut layout: Layout = input.parse()?;
|
|
|
|
|
2020-12-11 15:09:10 +01:00
|
|
|
let occupied_threshold = 4;
|
|
|
|
layout.converge(occupied_threshold, Layout::count_adjacent);
|
|
|
|
|
|
|
|
Ok(layout.occupied_seats())
|
|
|
|
}
|
|
|
|
|
2020-12-14 18:08:16 +01:00
|
|
|
fn part2(input: &str) -> Result<usize> {
|
2020-12-11 15:09:10 +01:00
|
|
|
let mut layout: Layout = input.parse()?;
|
|
|
|
|
|
|
|
let occupied_threshold = 5;
|
|
|
|
layout.converge(occupied_threshold, Layout::count_line_of_sight);
|
2020-12-11 14:37:14 +01:00
|
|
|
|
|
|
|
Ok(layout.occupied_seats())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
|
|
enum Cell {
|
|
|
|
EmptySeat,
|
|
|
|
Floor,
|
|
|
|
OccupiedSeat,
|
|
|
|
}
|
|
|
|
|
|
|
|
type Grid = Vec<Vec<Cell>>;
|
|
|
|
|
|
|
|
struct Layout {
|
|
|
|
grid: Grid,
|
|
|
|
height: usize,
|
|
|
|
width: usize,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Layout {
|
|
|
|
/// Steps one round in the simulation, returns the previous grid
|
2020-12-11 15:09:10 +01:00
|
|
|
fn step(
|
|
|
|
&mut self,
|
|
|
|
occupied_threshold: u8,
|
|
|
|
adj_count: fn(&Self, usize, usize, Cell) -> u8,
|
|
|
|
) -> bool {
|
2020-12-11 14:37:14 +01:00
|
|
|
let grid = &self.grid;
|
|
|
|
|
|
|
|
let mut new = grid.clone();
|
2020-12-11 15:09:10 +01:00
|
|
|
let mut changed = false;
|
2020-12-11 14:37:14 +01:00
|
|
|
|
|
|
|
for i in 0..self.height {
|
|
|
|
for j in 0..self.width {
|
|
|
|
let cell = self[i][j];
|
|
|
|
|
|
|
|
match cell {
|
|
|
|
Cell::EmptySeat => {
|
2020-12-11 15:09:10 +01:00
|
|
|
if adj_count(&self, i, j, Cell::OccupiedSeat) == 0 {
|
2020-12-11 14:37:14 +01:00
|
|
|
new[i][j] = Cell::OccupiedSeat;
|
2020-12-11 15:09:10 +01:00
|
|
|
changed = true;
|
2020-12-11 14:37:14 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
Cell::OccupiedSeat => {
|
2020-12-11 15:09:10 +01:00
|
|
|
if adj_count(&self, i, j, Cell::OccupiedSeat) >= occupied_threshold {
|
2020-12-11 14:37:14 +01:00
|
|
|
new[i][j] = Cell::EmptySeat;
|
2020-12-11 15:09:10 +01:00
|
|
|
changed = true;
|
2020-12-11 14:37:14 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-11 15:09:10 +01:00
|
|
|
self.grid = new;
|
|
|
|
|
|
|
|
changed
|
2020-12-11 14:37:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Steps through the simulation until a fixpoint is reached
|
2020-12-11 15:09:10 +01:00
|
|
|
fn converge(&mut self, occupied_threshold: u8, adj_count: fn(&Self, usize, usize, Cell) -> u8) {
|
|
|
|
while self.step(occupied_threshold, adj_count) {}
|
2020-12-11 14:37:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
const OFFSETS: &'static [(i8, i8)] = &[
|
|
|
|
(0, -1),
|
|
|
|
(0, 1),
|
|
|
|
(-1, 0),
|
|
|
|
(1, 0),
|
|
|
|
(-1, -1),
|
|
|
|
(-1, 1),
|
|
|
|
(1, -1),
|
|
|
|
(1, 1),
|
|
|
|
];
|
|
|
|
|
|
|
|
fn count_adjacent(&self, i: usize, j: usize, value: Cell) -> u8 {
|
|
|
|
let mut count = 0;
|
|
|
|
|
|
|
|
for (di, dj) in Self::OFFSETS {
|
|
|
|
let (i, j) = (i.wrapping_add(*di as usize), j.wrapping_add(*dj as usize));
|
|
|
|
|
|
|
|
// only check seat if it's in bounds
|
|
|
|
count += self
|
|
|
|
.grid
|
|
|
|
.get(i)
|
|
|
|
.map(|line| line.get(j))
|
|
|
|
.flatten()
|
|
|
|
.map(|&cell| if cell == value { 1 } else { 0 })
|
|
|
|
.unwrap_or(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
count
|
|
|
|
}
|
|
|
|
|
2020-12-11 15:09:10 +01:00
|
|
|
fn count_line_of_sight(&self, i: usize, j: usize, value: Cell) -> u8 {
|
|
|
|
let mut count = 0;
|
|
|
|
|
|
|
|
for (di, dj) in Self::OFFSETS {
|
|
|
|
let mut distance = 1;
|
|
|
|
|
|
|
|
let diff = loop {
|
|
|
|
let (di, dj) = (di * distance, dj * distance);
|
|
|
|
|
|
|
|
let (i, j) = (i.wrapping_add(di as usize), j.wrapping_add(dj as usize));
|
|
|
|
|
|
|
|
let cell = self.grid.get(i).map(|line| line.get(j)).flatten();
|
|
|
|
|
|
|
|
match cell {
|
|
|
|
// keep going, the next seat is farther away
|
|
|
|
Some(Cell::Floor) => distance += 1,
|
|
|
|
// found the kind of seat we care about
|
|
|
|
Some(&seat) if seat == value => break 1,
|
|
|
|
// found a seat that blocks line of sight, or reached out of bounds
|
|
|
|
Some(_) | None => break 0,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// only check seat if it's in bounds
|
|
|
|
count += diff;
|
|
|
|
}
|
|
|
|
|
|
|
|
count
|
|
|
|
}
|
|
|
|
|
2020-12-11 14:37:14 +01:00
|
|
|
fn occupied_seats(&self) -> usize {
|
|
|
|
self.grid
|
|
|
|
.iter()
|
|
|
|
.map(|line| {
|
|
|
|
line.iter()
|
|
|
|
.filter(|seat| **seat == Cell::OccupiedSeat)
|
|
|
|
.count()
|
|
|
|
})
|
|
|
|
.sum()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl std::ops::Index<usize> for Layout {
|
|
|
|
type Output = Vec<Cell>;
|
|
|
|
|
|
|
|
fn index(&self, idx: usize) -> &Self::Output {
|
|
|
|
&self.grid[idx]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl std::ops::IndexMut<usize> for Layout {
|
|
|
|
fn index_mut(&mut self, idx: usize) -> &mut Self::Output {
|
|
|
|
&mut self.grid[idx]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl std::str::FromStr for Layout {
|
2020-12-14 18:08:16 +01:00
|
|
|
type Err = anyhow::Error;
|
2020-12-11 14:37:14 +01:00
|
|
|
|
2020-12-14 18:08:16 +01:00
|
|
|
fn from_str(s: &str) -> Result<Self> {
|
2020-12-11 14:37:14 +01:00
|
|
|
let grid = s
|
|
|
|
.lines()
|
|
|
|
.map(|line| {
|
|
|
|
line.chars()
|
|
|
|
.map(|c| match c {
|
|
|
|
'.' => Ok(Cell::Floor),
|
|
|
|
'L' => Ok(Cell::EmptySeat),
|
|
|
|
'#' => Ok(Cell::OccupiedSeat),
|
2020-12-14 18:08:16 +01:00
|
|
|
_ => Err(anyhow!("unknown char `{}`", c)),
|
2020-12-11 14:37:14 +01:00
|
|
|
})
|
|
|
|
.collect()
|
|
|
|
})
|
2020-12-14 18:08:16 +01:00
|
|
|
.collect::<Result<Grid>>()?;
|
2020-12-11 14:37:14 +01:00
|
|
|
|
|
|
|
let height = grid.len();
|
|
|
|
let width = grid[0].len();
|
|
|
|
|
|
|
|
Ok(Self {
|
|
|
|
grid,
|
|
|
|
height,
|
|
|
|
width,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
const PROVIDED: &str = include_str!("../input/day11_provided.txt");
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn part1_provided() {
|
|
|
|
assert_eq!(part1(PROVIDED).unwrap(), 37);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn part1_real() {
|
|
|
|
assert_eq!(part1(INPUT).unwrap(), 2427);
|
|
|
|
}
|
2020-12-11 15:09:10 +01:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn part2_provided() {
|
|
|
|
assert_eq!(part2(PROVIDED).unwrap(), 26);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn part2_real() {
|
|
|
|
assert_eq!(part2(INPUT).unwrap(), 2199);
|
|
|
|
}
|
2020-12-11 14:37:14 +01:00
|
|
|
}
|