From 867678fd50153e29ecd72006521098a9eba87090 Mon Sep 17 00:00:00 2001 From: Kevin Caffrey Date: Sat, 16 Dec 2023 11:59:54 -0500 Subject: [PATCH] day 16, but with brute force --- README.md | 3 +- data/examples/16.txt | 10 ++ src/bin/16.rs | 327 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 339 insertions(+), 1 deletion(-) create mode 100644 data/examples/16.txt create mode 100644 src/bin/16.rs diff --git a/README.md b/README.md index 3685583..696dc7d 100644 --- a/README.md +++ b/README.md @@ -46,8 +46,9 @@ Solutions for [Advent of Code](https://adventofcode.com/) in [Rust](https://www. | [Day 13](./src/bin/13.rs) | `12.3µs` | `15.9µs` | | [Day 14](./src/bin/14.rs) | `25.0µs` | `4.5ms` | | [Day 15](./src/bin/15.rs) | `20.4µs` | `85.9µs` | +| [Day 16](./src/bin/16.rs) | `1.6ms` | `87.7ms` | -**Total: 7.02ms** +**Total: 96.32ms** --- diff --git a/data/examples/16.txt b/data/examples/16.txt new file mode 100644 index 0000000..d6805ce --- /dev/null +++ b/data/examples/16.txt @@ -0,0 +1,10 @@ +.|...\.... +|.-.\..... +.....|-... +........|. +.......... +.........\ +..../.\\.. +.-.-/..|.. +.|....-|.\ +..//.|.... diff --git a/src/bin/16.rs b/src/bin/16.rs new file mode 100644 index 0000000..4d12cec --- /dev/null +++ b/src/bin/16.rs @@ -0,0 +1,327 @@ +use std::{ + collections::{HashSet, VecDeque}, + fmt::{Debug, Display, Write}, + str::FromStr, +}; + +use rayon::iter::{ParallelBridge, ParallelIterator}; +use thiserror::Error; + +advent_of_code::solution!(16); + +pub fn part_one(input: &str) -> Option { + let grid: Grid = input.parse().expect("valid input"); + Some(energize_count(&grid, coord!(0, 0), Direction::East)) +} + +pub fn part_two(input: &str) -> Option { + let grid: Grid = input.parse().expect("valid input"); + (0..grid.height) + .map(|h| (coord!(h, 0), Direction::East)) + .chain((0..grid.height).map(|h| (coord!(h, grid.width - 1), Direction::West))) + .chain((0..grid.width).map(|w| (coord!(0, w), Direction::South))) + .chain((0..grid.width).map(|w| (coord!(grid.height - 1, w), Direction::North))) + .par_bridge() + .map(|(c, d)| energize_count(&grid, c, d)) + .max() +} + +fn energize_count(grid: &Grid, start: Coordinate, start_dir: Direction) -> u32 { + let mut visited = HashSet::new(); + let mut visited_coords = HashSet::new(); + let mut queue = VecDeque::new(); + visited.insert((start, start_dir)); + visited_coords.insert(start); + queue.push_back((start, start_dir)); + while let Some((cur, dir)) = queue.pop_front() { + for (next, next_dir) in grid.neighbors(cur, dir) { + if visited.insert((next, next_dir)) { + visited_coords.insert(next); + queue.push_back((next, next_dir)); + } + } + } + visited_coords.len() as u32 +} + +struct Grid { + tiles: Vec>, + width: usize, + height: usize, +} + +#[derive(Copy, Clone, PartialEq, Eq)] +enum Tile { + Empty, + RightMirror, + LeftMirror, + VerticalSplitter, + HorizontalSplitter, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +struct Coordinate { + row: usize, + col: usize, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +enum Direction { + North, + South, + East, + West, +} + +impl Grid { + pub fn neighbors( + &self, + coord: Coordinate, + dir: Direction, + ) -> impl Iterator + '_ { + std::iter::once(self.get_tile(coord)) + .flatten() + .flat_map(move |tile| tile.next(dir)) + .filter_map(move |dir| self.move_in_dir(coord, dir).map(|c| (c, dir))) + } + + fn get_tile(&self, coord: Coordinate) -> Option { + self.tiles + .get(coord.row) + .and_then(|row| row.get(coord.col)) + .copied() + } + + fn move_in_dir(&self, coord: Coordinate, dir: Direction) -> Option { + coord + .move_in_dir(dir) + .filter(|&c| c.row < self.height && c.col < self.width) + } +} + +impl Tile { + pub fn next(self, dir: Direction) -> impl Iterator { + let mut next = [None, None, None]; + if self.can_go_straight(dir) { + next[0] = Some(dir); + } + if self.can_turn_left(dir) { + next[1] = Some(dir.rotate_left()); + } + if self.can_turn_right(dir) { + next[2] = Some(dir.rotate_right()); + } + next.into_iter().flatten() + } + + const fn can_go_straight(self, dir: Direction) -> bool { + matches!( + (self, dir), + (Self::Empty, _) + | (Self::VerticalSplitter, Direction::South) + | (Self::VerticalSplitter, Direction::North) + | (Self::HorizontalSplitter, Direction::East) + | (Self::HorizontalSplitter, Direction::West) + ) + } + + const fn can_turn_left(self, dir: Direction) -> bool { + matches!( + (self, dir), + (Self::VerticalSplitter, Direction::East) + | (Self::VerticalSplitter, Direction::West) + | (Self::HorizontalSplitter, Direction::North) + | (Self::HorizontalSplitter, Direction::South) + | (Self::LeftMirror, Direction::North) + | (Self::LeftMirror, Direction::South) + | (Self::RightMirror, Direction::East) + | (Self::RightMirror, Direction::West) + ) + } + + const fn can_turn_right(self, dir: Direction) -> bool { + matches!( + (self, dir), + (Self::VerticalSplitter, Direction::East) + | (Self::VerticalSplitter, Direction::West) + | (Self::HorizontalSplitter, Direction::North) + | (Self::HorizontalSplitter, Direction::South) + | (Self::LeftMirror, Direction::East) + | (Self::LeftMirror, Direction::West) + | (Self::RightMirror, Direction::North) + | (Self::RightMirror, Direction::South) + ) + } +} + +impl Coordinate { + const fn new(row: usize, col: usize) -> Self { + Self { row, col } + } + + const fn move_in_dir(self, dir: Direction) -> Option { + Some(match dir { + Direction::North => { + if self.row == 0 { + return None; + } + Self::new(self.row - 1, self.col) + } + Direction::South => Self::new(self.row + 1, self.col), + Direction::East => Self::new(self.row, self.col + 1), + Direction::West => { + if self.col == 0 { + return None; + } + Self::new(self.row, self.col - 1) + } + }) + } +} + +impl Direction { + const fn rotate_left(self) -> Self { + match self { + Direction::North => Direction::West, + Direction::South => Direction::East, + Direction::East => Direction::North, + Direction::West => Direction::South, + } + } + + const fn rotate_right(self) -> Self { + match self { + Direction::North => Direction::East, + Direction::South => Direction::West, + Direction::East => Direction::South, + Direction::West => Direction::North, + } + } +} + +#[derive(Error, Debug)] +enum ParseGridError { + #[error("invalid tile character: {0}")] + InvalidCharacter(char), + + #[error("only one row was in the input, expected more than one row")] + OnlyOneRowFound, +} + +impl FromStr for Grid { + type Err = ParseGridError; + + fn from_str(s: &str) -> Result { + let s = s.as_bytes(); + let width = s + .iter() + .position(|&ch| ch == b'\n') + .ok_or(ParseGridError::OnlyOneRowFound)?; + let mut rows = Vec::new(); + for line in s.split(|&ch| ch == b'\n') { + if line.is_empty() { + continue; + } + rows.push( + line.iter() + .map(|&ch| { + Ok(match ch { + b'.' => Tile::Empty, + b'\\' => Tile::LeftMirror, + b'/' => Tile::RightMirror, + b'|' => Tile::VerticalSplitter, + b'-' => Tile::HorizontalSplitter, + _ => return Err(ParseGridError::InvalidCharacter(ch.into())), + }) + }) + .collect::>()?, + ); + } + Ok(Self { + width, + height: rows.len(), + tiles: rows, + }) + } +} + +impl Display for Grid { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for row in &self.tiles { + for tile in row { + write!(f, "{}", tile)?; + } + f.write_char('\n')?; + } + Ok(()) + } +} + +impl Display for Tile { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Tile::Empty => f.write_char('.'), + Tile::RightMirror => f.write_char('/'), + Tile::LeftMirror => f.write_char('\\'), + Tile::VerticalSplitter => f.write_char('|'), + Tile::HorizontalSplitter => f.write_char('-'), + } + } +} + +impl Debug for Tile { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(&self, f) + } +} + +#[macro_export] +macro_rules! coord { + ($x:expr, $y:expr) => { + Coordinate::new($x, $y) + }; +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_part_one() { + let result = part_one(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(46)); + } + + #[test] + fn test_part_two() { + let result = part_two(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(51)); + } + + #[test] + fn test_grid_neighbors() { + use Direction::{East, North, South, West}; + let grid: Grid = advent_of_code::template::read_file("examples", DAY) + .parse() + .unwrap(); + let neighbors_vec = |coord, dir| grid.neighbors(coord, dir).collect::>(); + assert_eq!(neighbors_vec(coord!(0, 0), North), vec![]); + assert_eq!(neighbors_vec(coord!(0, 0), West), vec![]); + assert_eq!( + neighbors_vec(coord!(0, 0), East), + vec![(coord!(0, 1), East)] + ); + assert_eq!( + neighbors_vec(coord!(0, 1), East), + vec![(coord!(1, 1), South)] + ); + assert_eq!( + neighbors_vec(coord!(1, 2), South), + vec![(coord!(1, 3), East), (coord!(1, 1), West)] + ); + assert_eq!( + neighbors_vec(coord!(1, 4), West), + vec![(coord!(0, 4), North)] + ); + } +}