Day 4: Printing Department
Megathread guidelines
- Keep top level comments as only solutions, if you want to say something other than a solution put it in a new post. (replies to comments can be whatever)
- You can send code in code blocks by using three backticks, the code, and then three backticks or use something such as https://topaz.github.io/paste/ if you prefer sending it through a URL
FAQ
- What is this?: Here is a post with a large amount of details: https://programming.dev/post/6637268
- Where do I participate?: https://adventofcode.com/
- Is there a leaderboard for the community?: We have a programming.dev leaderboard with the info on how to join in this post: https://programming.dev/post/6631465


Rust
I pulled out some code from last year to make representing 2D grids as a vector easier, so this was quite straightforward. 2.5ms runtime (including reading/parsing the input twice cos of TDD).
impl Grid { fn neighbour(&self, i: usize, dir: Direction) -> Option<bool> { let width = self.width; let length = self.grid.len(); use Direction::*; match dir { N if i >= width => Some(i - width), NE if i >= width && i % width != width - 1 => Some(i - width + 1), E if i % width != width - 1 => Some(i + 1), SE if i + width + 1 < length && i % width != width - 1 => Some(i + width + 1), S if i + width < length => Some(i + width), SW if i + width - 1 < length && !i.is_multiple_of(width) => Some(i + width - 1), W if !i.is_multiple_of(width) => Some(i - 1), NW if i >= width && !i.is_multiple_of(width) => Some(i - width - 1), _ => None, }; .map(|i| self.grid[i]) } #[rustfmt::skip] fn cell_accessible(&self, i: usize) -> bool { Direction::ALL .iter() .filter(|&&dir| self.neighbour(i, dir).unwrap_or(false)) .count() < 4 } fn num_accessible(&self) -> usize { self.grid .iter() .enumerate() .filter(|&(i, &is_occupied)| is_occupied && self.cell_accessible(i)) .count() } fn remove_accessible(&mut self) -> Option<usize> { let removables = self .grid .iter() .enumerate() .filter_map(|(i, &is_occupied)| (is_occupied && self.cell_accessible(i)).then_some(i)) .collect::<Vec<_>>(); let count = removables.len(); if count > 0 { for idx in removables { self.grid[idx] = false; } Some(count) } else { None } } fn remove_recursive(&mut self) -> usize { let mut total_removed = 0; while let Some(removed) = self.remove_accessible() { total_removed += removed; } total_removed } }Full code
use std::{fs, str::FromStr}; use color_eyre::eyre::{Report, Result}; #[derive(Debug, Copy, Clone)] enum Direction { N, NE, E, SE, S, SW, W, NW, } impl Direction { const ALL: [Direction; 8] = [ Direction::N, Direction::NE, Direction::E, Direction::SE, Direction::S, Direction::SW, Direction::W, Direction::NW, ]; } #[derive(Debug, PartialEq, Eq, Clone)] struct Grid { grid: Vec<bool>, width: usize, height: usize, } impl FromStr for Grid { type Err = Report; fn from_str(s: &str) -> Result<Self, Self::Err> { let grid: Vec<_> = s .chars() .filter_map(|ch| match ch { '.' => Some(false), '@' => Some(true), '\n' => None, _ => panic!("Invalid input"), }) .collect(); let width = s .chars() .position(|ch| ch == '\n') .ok_or_else(|| Report::msg("grid width cannot be zero, or one line"))?; let height = grid.len() / width; Ok(Self { grid, width, height, }) } } impl Grid { fn neighbour(&self, i: usize, dir: Direction) -> Option<bool> { let width = self.width; let length = self.grid.len(); use Direction::*; match dir { N if i >= width => Some(i - width), NE if i >= width && i % width != width - 1 => Some(i - width + 1), E if i % width != width - 1 => Some(i + 1), SE if i + width + 1 < length && i % width != width - 1 => Some(i + width + 1), S if i + width < length => Some(i + width), SW if i + width - 1 < length && !i.is_multiple_of(width) => Some(i + width - 1), W if !i.is_multiple_of(width) => Some(i - 1), NW if i >= width && !i.is_multiple_of(width) => Some(i - width - 1), _ => None, }; .map(|i| self.grid[i]) } #[rustfmt::skip] fn cell_accessible(&self, i: usize) -> bool { Direction::ALL .iter() .filter(|&&dir| self.neighbour(i, dir).unwrap_or(false)) .count() < 4 } fn num_accessible(&self) -> usize { self.grid .iter() .enumerate() .filter(|&(i, &is_occupied)| is_occupied && self.cell_accessible(i)) .count() } fn remove_accessible(&mut self) -> Option<usize> { let removables = self .grid .iter() .enumerate() .filter_map(|(i, &is_occupied)| (is_occupied && self.cell_accessible(i)).then_some(i)) .collect::<Vec<_>>(); let count = removables.len(); if count > 0 { for idx in removables { self.grid[idx] = false; } Some(count) } else { None } } fn remove_recursive(&mut self) -> usize { let mut total_removed = 0; while let Some(removed) = self.remove_accessible() { total_removed += removed; } total_removed } } fn part1(filepath: &str) -> Result<usize> { let input = fs::read_to_string(filepath)?; let grid = Grid::from_str(&input)?; Ok(grid.num_accessible()) } fn part2(filepath: &str) -> Result<usize> { let input = fs::read_to_string(filepath)?; let mut grid = Grid::from_str(&input)?; Ok(grid.remove_recursive()) } fn main() -> Result<()> { color_eyre::install()?; println!("Part 1: {}", part1("d04/input.txt")?); println!("Part 2: {}", part2("d04/input.txt")?); Ok(()) }