From 427a0c766b293b451c37fe4f87c032971caf5a2b Mon Sep 17 00:00:00 2001 From: Keenan Tims Date: Fri, 15 Dec 2023 23:42:43 -0800 Subject: [PATCH] day16: much cleaner, nicely factored solution, and much faster --- 16/src/main.rs | 245 +++++++++++++++++++++++++++---------------------- 1 file changed, 134 insertions(+), 111 deletions(-) diff --git a/16/src/main.rs b/16/src/main.rs index 6bafcfd..6611bc7 100644 --- a/16/src/main.rs +++ b/16/src/main.rs @@ -28,12 +28,72 @@ fn main() { // PARSE +enum Interaction { + One(FromDirection), + Two((FromDirection, FromDirection)), +} + #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] enum FromDirection { - Left, - Above, - Right, - Below, + Left = 0, + Above = 1, + Right = 2, + Below = 3, +} + +impl FromDirection { + fn mask(&self) -> u8 { + 1 << *self as u8 + } + // return the new pos for a ray that will be from the direction. + // a ray 'from' left 'goes' right + fn goes_pos(&self, pos: (i64, i64)) -> (i64, i64) { + match self { + Self::Left => (pos.0 + 1, pos.1), + Self::Right => (pos.0 - 1, pos.1), + Self::Above => (pos.0, pos.1 + 1), + Self::Below => (pos.0, pos.1 - 1), + } + } + fn reflect_ne(&self) -> Self { + match self { + Self::Left => Self::Below, + Self::Right => Self::Above, + Self::Above => Self::Right, + Self::Below => Self::Left, + } + } + fn opposite(&self) -> FromDirection { + match self { + Self::Left => Self::Right, + Self::Right => Self::Left, + Self::Above => Self::Below, + Self::Below => Self::Above, + } + } + fn reflect_se(&self) -> FromDirection { + self.reflect_ne().opposite() + } + fn interact(&self, tile: char) -> Interaction { + match tile { + '.' => Interaction::One(*self), + '/' => Interaction::One(self.reflect_ne()), + '\\' => Interaction::One(self.reflect_se()), + '|' => match self { + FromDirection::Above | FromDirection::Below => Interaction::One(*self), + FromDirection::Left | FromDirection::Right => { + Interaction::Two((FromDirection::Above, FromDirection::Below)) + } + }, + '-' => match self { + FromDirection::Left | FromDirection::Right => Interaction::One(*self), + FromDirection::Above | FromDirection::Below => { + Interaction::Two((FromDirection::Left, FromDirection::Right)) + } + }, + c => unimplemented!("invalid tile {}", c), + } + } } impl Display for FromDirection { @@ -52,44 +112,55 @@ struct Contraption { } struct VisitState { - energized: Vec>, - visited_rays: HashSet<((i64, i64), FromDirection)>, + visited_from: Vec>, } impl VisitState { - fn score(&self) -> u64 { - self.energized.iter().flatten().filter(|c| **c).count() as u64 + fn visit(&mut self, pos: (i64, i64), dir: FromDirection) -> bool { + let pos_state = &mut self.visited_from[pos.1 as usize][pos.0 as usize]; + if *pos_state & dir.mask() > 0 { + false + } else { + *pos_state |= dir.mask(); + true + } } + fn score(&self) -> u64 { + self.visited_from + .iter() + .flatten() + .filter(|c| **c != 0) + .count() as u64 + } + #[allow(dead_code)] fn dump(&self) { println!("Score {}:", self.score()); - for line in &self.energized { + for line in &self.visited_from { println!( " {}", - String::from_iter(line.iter().map(|b| if *b { '#' } else { '.' })) + String::from_iter(line.iter().map(|b| if *b == 0 { '.' } else { '#' })) ); } } } - impl Contraption { - fn cast_ray<'a>( - &'a mut self, - state: &'a mut VisitState, - pos: (i64, i64), - dir: FromDirection, - ) { - let mut new_rays = self.cast_ray_inner(state, pos, dir); - - loop { + fn height(&self) -> i64 { + self.tiles.len() as i64 + } + fn width(&self) -> i64 { + self.tiles[0].len() as i64 + } + fn cast_ray<'a>(&'a mut self, pos: (i64, i64), dir: FromDirection) -> VisitState { + let mut state = self.empty_state(); + let mut new_rays = self.cast_ray_inner(&mut state, pos, dir); + while new_rays.len() != 0 { new_rays = new_rays .iter() - .flat_map(|(pos, dir)| self.cast_ray_inner(state, *pos, *dir)) + .flat_map(|(pos, dir)| self.cast_ray_inner(&mut state, *pos, *dir)) .collect(); - if new_rays.len() == 0 { - break; - } } + state } fn cast_ray_inner<'a>( &'a mut self, @@ -97,68 +168,29 @@ impl Contraption { mut pos: (i64, i64), mut dir: FromDirection, ) -> Vec<((i64, i64), FromDirection)> { - let width = state.energized[0].len(); - let height = state.energized.len(); let mut new_rays = Vec::new(); - while pos.0 >= 0 && pos.1 >= 0 && pos.0 < width as i64 && pos.1 < height as i64 { - // visit pos - state.energized[pos.1 as usize][pos.0 as usize] = true; - if !state.visited_rays.insert((pos, dir)) { + + while pos.0 >= 0 && pos.1 >= 0 && pos.0 < self.width() && pos.1 < self.height() { + if !state.visit(pos, dir) { break; } - (pos, dir) = match self.tiles[pos.1 as usize][pos.0 as usize] { - '.' => match dir { - FromDirection::Left => ((pos.0 + 1, pos.1), dir), - FromDirection::Right => ((pos.0 - 1, pos.1), dir), - FromDirection::Above => ((pos.0, pos.1 + 1), dir), - FromDirection::Below => ((pos.0, pos.1 - 1), dir), - }, - '/' => match dir { - // from left, go up, from below - FromDirection::Left => ((pos.0, pos.1 - 1), FromDirection::Below), - // from up, go left, from the right - FromDirection::Above => ((pos.0 - 1, pos.1), FromDirection::Right), - // from right, go down, from above - FromDirection::Right => ((pos.0, pos.1 + 1), FromDirection::Above), - // from below, go right, from left - FromDirection::Below => ((pos.0 + 1, pos.1), FromDirection::Left), - }, - '\\' => match dir { - FromDirection::Left => ((pos.0, pos.1 + 1), FromDirection::Above), - FromDirection::Above => ((pos.0 + 1, pos.1), FromDirection::Left), - FromDirection::Right => ((pos.0, pos.1 - 1), FromDirection::Below), - FromDirection::Below => ((pos.0 - 1, pos.1), FromDirection::Right), - }, - '-' => match dir { - FromDirection::Left => ((pos.0 + 1, pos.1), dir), - FromDirection::Right => ((pos.0 - 1, pos.1), dir), - FromDirection::Above | FromDirection::Below => { - new_rays.push(((pos.0 + 1, pos.1), FromDirection::Left)); - ((pos.0 - 1, pos.1), FromDirection::Right) - } - }, - '|' => match dir { - FromDirection::Above => ((pos.0, pos.1 + 1), dir), - FromDirection::Below => ((pos.0, pos.1 - 1), dir), - FromDirection::Left | FromDirection::Right => { - new_rays.push(((pos.0, pos.1 + 1), FromDirection::Above)); - ((pos.0, pos.1 - 1), FromDirection::Below) - } - }, - c => unimplemented!("invalid character {}", c), - } + dir = match dir.interact(self.tiles[pos.1 as usize][pos.0 as usize]) { + Interaction::One(dir) => dir, + Interaction::Two((dir1, dir2)) => { + new_rays.push((pos, dir1)); + dir2 + } + }; + pos = dir.goes_pos(pos); } new_rays } fn empty_state(&self) -> VisitState { - let mut energized = Vec::new(); - for _ in 0..self.tiles.len() { - energized.push(Vec::from_iter(repeat(false).take(self.tiles[0].len()))); - } - VisitState { - energized, - visited_rays: HashSet::new(), + let mut visited_from = Vec::new(); + for _ in 0..self.height() { + visited_from.push(Vec::from_iter(repeat(0).take(self.width() as usize))); } + VisitState { visited_from } } } @@ -176,48 +208,39 @@ impl From> for Contraption { fn problem1(input: Lines) -> u64 { let mut contraption = Contraption::from(input); - let mut state = contraption.empty_state(); - contraption.cast_ray(&mut state, (0,0), FromDirection::Left); - - state.energized.iter().flatten().filter(|c| **c).count() as u64 + contraption.cast_ray((0, 0), FromDirection::Left).score() } // PROBLEM 2 solution fn problem2(input: Lines) -> u64 { let mut contraption = Contraption::from(input); - let mut max_tiles = 0u64; - for y in 0..contraption.tiles.len() as i64 { - let mut left_state = contraption.empty_state(); - contraption.cast_ray(&mut left_state, (0, y), FromDirection::Left); - let mut right_state = contraption.empty_state(); - contraption.cast_ray( - &mut right_state, - (contraption.tiles[0].len() as i64 - 1, y), - FromDirection::Right, - ); - max_tiles = std::cmp::max( + let rows_max = (0..contraption.height()).fold(0, |max_tiles, y| { + std::cmp::max( max_tiles, - std::cmp::max(left_state.score(), right_state.score()), - ); - } - for x in 0..contraption.tiles[0].len() as i64 { - let mut top_state = contraption.empty_state(); - contraption.cast_ray(&mut top_state, (x, 0), FromDirection::Above); - let mut bottom_state = contraption.empty_state(); - contraption.cast_ray( - &mut bottom_state, - (x, contraption.tiles.len() as i64 - 1), - FromDirection::Below, - ); - max_tiles = std::cmp::max( - max_tiles, - std::cmp::max(top_state.score(), bottom_state.score()), - ); - } + std::cmp::max( + contraption.cast_ray((0, y), FromDirection::Left).score(), + contraption + .cast_ray((contraption.width() - 1, y), FromDirection::Right) + .score(), + ), + ) + }); - max_tiles + let cols_max = (0..contraption.width()).fold(0, |max_tiles, x| { + std::cmp::max( + max_tiles, + std::cmp::max( + contraption.cast_ray((x, 0), FromDirection::Above).score(), + contraption + .cast_ray((x, contraption.height() - 1), FromDirection::Below) + .score(), + ), + ) + }); + + std::cmp::max(rows_max, cols_max) } #[cfg(test)]