diff --git a/README.md b/README.md index 6ea2300..fee2ef3 100644 --- a/README.md +++ b/README.md @@ -27,19 +27,19 @@ Solutions for [Advent of Code](https://adventofcode.com/) in [Rust](https://www. | Day | Part 1 | Part 2 | | :---: | :---: | :---: | -| [Day 1](./src/bin/01.rs) | `36.8µs` | `35.1µs` | -| [Day 2](./src/bin/02.rs) | `41.6µs` | `41.4µs` | -| [Day 3](./src/bin/03.rs) | `83.7µs` | `98.7µs` | -| [Day 4](./src/bin/04.rs) | `49.3µs` | `50.0µs` | -| [Day 5](./src/bin/05.rs) | `20.5µs` | `24.1µs` | -| [Day 6](./src/bin/06.rs) | `208.0ns` | `102.0ns` | -| [Day 7](./src/bin/07.rs) | `97.3µs` | `94.0µs` | -| [Day 8](./src/bin/08.rs) | `72.7µs` | `160.2µs` | -| [Day 9](./src/bin/09.rs) | `58.2µs` | `58.4µs` | -| [Day 10](./src/bin/10.rs) | `320.4µs` | `640.5µs` | -| [Day 11](./src/bin/11.rs) | `16.6µs` | `15.9µs` | - -**Total: 2.02ms** +| [Day 1](./src/bin/01.rs) | `34.3µs` | `35.6µs` | +| [Day 2](./src/bin/02.rs) | `42.3µs` | `41.9µs` | +| [Day 3](./src/bin/03.rs) | `84.6µs` | `99.4µs` | +| [Day 4](./src/bin/04.rs) | `49.0µs` | `49.5µs` | +| [Day 5](./src/bin/05.rs) | `20.4µs` | `24.2µs` | +| [Day 6](./src/bin/06.rs) | `221.0ns` | `104.0ns` | +| [Day 7](./src/bin/07.rs) | `103.4µs` | `96.2µs` | +| [Day 8](./src/bin/08.rs) | `72.2µs` | `160.0µs` | +| [Day 9](./src/bin/09.rs) | `53.1µs` | `47.1µs` | +| [Day 10](./src/bin/10.rs) | `262.9µs` | `279.8µs` | +| [Day 11](./src/bin/11.rs) | `16.2µs` | `15.8µs` | + +**Total: 1.59ms** --- diff --git a/src/bin/10.rs b/src/bin/10.rs index c91e215..29f4ee7 100644 --- a/src/bin/10.rs +++ b/src/bin/10.rs @@ -1,4 +1,4 @@ -use std::{collections::HashSet, str::FromStr}; +use std::str::FromStr; use either::Either; use tinyvec::ArrayVec; @@ -7,130 +7,23 @@ advent_of_code::solution!(10); pub fn part_one(input: &str) -> Option { let field = input.parse::().expect("valid input"); - field - .find_definite_loop_position() - .map(|(_, distance)| distance) + field.find_loop_length() } pub fn part_two(input: &str) -> Option { - let field = input.parse::().expect("valid input"); + let mut field = input.parse::().expect("valid input"); + field.remove_non_loop_pipes(); - // Figure out where the loop is, and construct a new binary map of loop/not loop. - let (loop_position, _) = field.find_definite_loop_position()?; - let mut is_in_loop = vec![false; field.tiles.len()]; - is_in_loop[field.index(loop_position)?] = true; - is_in_loop[field.index(field.starting_position)?] = true; - let mut cur = ArrayVec::<[(Position, Position); 2]>::new(); - cur.extend( - field - .pipe_neighbors(loop_position) - .map(|n| (n, loop_position)), - ); - if cur.len() != 2 { - return None; - } - while !cur.is_empty() { - let mut next = ArrayVec::<[(Position, Position); 2]>::new(); - for (pos, from_pos) in cur - .iter() - .flat_map(|&(pos, last_pos)| field.next(pos, last_pos)) - { - let Some(index) = field.index(from_pos) else { - continue; - }; - if !is_in_loop[index] { - is_in_loop[index] = true; - if pos != field.starting_position { - next.push((pos, from_pos)); - } - } - } - cur = next; - } - - // Create a helper that will let us determine the size of a connected region bounded - // by "loop pipes". - let mut closed = HashSet::new(); - let mut flood_fill = |position: Position| -> u32 { - if closed.contains(&position) { - return 0; - } - let mut open = vec![position]; - closed.insert(position); - let mut tile_count = 0; - while let Some(cur) = open.pop() { - tile_count += 1; - for dir in Direction::cardinal() { - let next = cur.go(dir); - if let Some(index) = field.index(next) { - if !is_in_loop[index] && !closed.contains(&next) { - open.push(next); - closed.insert(next); - } - } - } - } - tile_count - }; - - // Find a spot on the loop where the inside/outside direction is known. - // We can start from the north on the column where the definite loop position is, - // and go south until we hit a loop tile. Then we know the direction north from - // that tile is "outside" and south is "inside". - // From there, we will walk clockwise direction around the loop, so we always know the - // outside direction. - let row = (0..field.rows).find(|&row| { - field - .index(Position { - row: row as isize, - col: loop_position.col, - }) - .filter(|&index| is_in_loop[index]) - .is_some() - })?; - let start = Position { - row: row as isize, - col: loop_position.col, - }; - let mut cur = start; - let mut outside_dir = match field.get(start) { - // Due to how the algorithm below works, we need to pretend - // we were coming from the south, so the outside is "west", - // in order to get properly rotated to north when we move. - Some(Tile::SouthEastPipe) => Direction::West, - _ => Direction::North, - }; let mut inside_count = 0; - loop { - for inside_pos in field.inside_neighbors(cur, outside_dir).filter(|&pos| { - field - .index(pos) - .filter(|&index| is_in_loop[index]) - .is_none() - }) { - inside_count += flood_fill(inside_pos); - } - let (next_dir, next_outside) = match (field.get(cur)?, outside_dir) { - (Tile::HorizontalPipe, Direction::North) => (Direction::East, Direction::North), - (Tile::HorizontalPipe, Direction::South) => (Direction::West, Direction::South), - (Tile::VerticalPipe, Direction::East) => (Direction::South, Direction::East), - (Tile::VerticalPipe, Direction::West) => (Direction::North, Direction::West), - (Tile::NorthEastPipe, Direction::South) => (Direction::North, Direction::West), - (Tile::NorthEastPipe, _) => (Direction::East, Direction::North), - (Tile::NorthWestPipe, Direction::East) => (Direction::West, Direction::South), - (Tile::NorthWestPipe, _) => (Direction::North, Direction::West), - (Tile::SouthEastPipe, Direction::West) => (Direction::East, Direction::North), - (Tile::SouthEastPipe, _) => (Direction::South, Direction::East), - (Tile::SouthWestPipe, Direction::North) => (Direction::South, Direction::East), - (Tile::SouthWestPipe, _) => (Direction::West, Direction::South), - _ => unreachable!(), - }; - let next_pos = cur.go(next_dir); - if next_pos == start { - break; + for row in 0..field.rows { + let mut inside = false; + for col in 0..field.cols { + match field.get(Position { row, col })? { + Tile::VerticalPipe | Tile::SouthEastPipe | Tile::SouthWestPipe => inside = !inside, + Tile::Ground if inside => inside_count += 1, + _ => {} + } } - cur = next_pos; - outside_dir = next_outside; } Some(inside_count) @@ -153,13 +46,12 @@ enum Direction { South, East, West, - None, } #[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)] struct Position { - row: isize, - col: isize, + row: usize, + col: usize, } #[derive(Debug, Clone, Eq, PartialEq)] @@ -181,126 +73,71 @@ impl Field { } pub fn index(&self, position: Position) -> Option { - if position.row < 0 - || position.col < 0 - || position.row >= self.rows as isize - || position.col >= self.cols as isize - { + if position.row >= self.rows || position.col >= self.cols { return None; } - let index = self.cols as isize * position.row + position.col; - Some(index as usize) + let index = self.cols * position.row + position.col; + Some(index) } - pub fn next( - &self, - pos: Position, - last_pos: Position, - ) -> impl Iterator { - let mut next = ArrayVec::<[(Position, Position); 4]>::new(); - use Tile::*; - match (self.get(pos), pos.direction_from(last_pos)) { - (Some(HorizontalPipe), Direction::East) => next.push((pos.go(Direction::West), pos)), - (Some(HorizontalPipe), Direction::West) => next.push((pos.go(Direction::East), pos)), - (Some(VerticalPipe), Direction::North) => next.push((pos.go(Direction::South), pos)), - (Some(VerticalPipe), Direction::South) => next.push((pos.go(Direction::North), pos)), - (Some(NorthEastPipe), Direction::North) => next.push((pos.go(Direction::East), pos)), - (Some(NorthEastPipe), Direction::East) => next.push((pos.go(Direction::North), pos)), - (Some(NorthWestPipe), Direction::North) => next.push((pos.go(Direction::West), pos)), - (Some(NorthWestPipe), Direction::West) => next.push((pos.go(Direction::North), pos)), - (Some(SouthWestPipe), Direction::South) => next.push((pos.go(Direction::West), pos)), - (Some(SouthWestPipe), Direction::West) => next.push((pos.go(Direction::South), pos)), - (Some(SouthEastPipe), Direction::South) => next.push((pos.go(Direction::East), pos)), - (Some(SouthEastPipe), Direction::East) => next.push((pos.go(Direction::South), pos)), - _ => {} + pub fn find_loop_length(&self) -> Option { + let mut distance = 0; + let mut dir = match self.get(self.starting_position)? { + Tile::HorizontalPipe | Tile::NorthEastPipe | Tile::SouthEastPipe => Direction::East, + Tile::VerticalPipe | Tile::NorthWestPipe => Direction::North, + Tile::SouthWestPipe => Direction::South, + Tile::Ground => return None, + }; + let mut cur = self.starting_position; + loop { + cur = cur.go(dir); + dir = self.get(cur)?.next_direction(dir); + distance += 1; + if cur == self.starting_position { + break; + } } - next.into_iter() + Some(distance / 2) } - pub fn find_definite_loop_position(&self) -> Option<(Position, u32)> { - let mut distance = 1; - let mut cur = ArrayVec::<[(Position, Position); 4]>::new(); - let mut definite_loop_position = None; - cur.extend( - self.pipe_neighbors(self.starting_position) - .map(|n| (n, self.starting_position)), - ); - while definite_loop_position.is_none() && !cur.is_empty() { - let mut next = ArrayVec::<[(Position, Position); 4]>::new(); - for (pos, from_pos) in cur - .iter() - .flat_map(|&(pos, last_pos)| self.next(pos, last_pos)) - { - if next.iter().any(|&(p, _)| pos == p) { - definite_loop_position = Some(pos); - break; - } - next.push((pos, from_pos)); + pub fn remove_non_loop_pipes(&mut self) { + let mut new_tiles = vec![Tile::Ground; self.tiles.len()]; + let starting_tile = self + .get(self.starting_position) + .expect("should be a starting tile"); + new_tiles[self + .index(self.starting_position) + .expect("should be a valid position")] = starting_tile; + let mut dir = match starting_tile { + Tile::HorizontalPipe | Tile::NorthEastPipe | Tile::SouthEastPipe => Direction::East, + Tile::VerticalPipe | Tile::NorthWestPipe if self.starting_position.row > 0 => { + Direction::North } - cur = next; - distance += 1; - } - let Some(pos) = definite_loop_position else { - return None; + Tile::NorthWestPipe => Direction::West, + Tile::VerticalPipe | Tile::SouthWestPipe => Direction::South, + Tile::Ground => unreachable!("starting tile shouldn't be ground"), }; - Some((pos, distance)) + let mut cur = self.starting_position; + loop { + cur = cur.go(dir); + let index = self.index(cur).expect("should be a valid position"); + let cur_tile = self.tiles[index]; + new_tiles[index] = cur_tile; + dir = cur_tile.next_direction(dir); + if cur == self.starting_position { + break; + } + } + self.tiles = new_tiles; } pub fn pipe_neighbors(&self, position: Position) -> impl Iterator { let tile = self.get(position).unwrap_or(Tile::Ground); tile.neighbors(position) } - - pub fn inside_neighbors( - &self, - position: Position, - outside_dir: Direction, - ) -> impl Iterator { - let mut ret = ArrayVec::<[Position; 2]>::new(); - match (self.get(position), outside_dir) { - (Some(Tile::HorizontalPipe), _) => ret.push(position.go(outside_dir.rev())), - (Some(Tile::VerticalPipe), _) => ret.push(position.go(outside_dir.rev())), - (Some(Tile::SouthWestPipe), Direction::North) => {} // Nothing inside here - (Some(Tile::SouthWestPipe), _) => { - ret.push(position.go(Direction::North)); - ret.push(position.go(Direction::East)); - } - (Some(Tile::SouthEastPipe), Direction::West) => {} // Nothing inside here - (Some(Tile::SouthEastPipe), _) => { - ret.push(position.go(Direction::North)); - ret.push(position.go(Direction::West)); - } - (Some(Tile::NorthWestPipe), Direction::East) => {} // Nothing inside here - (Some(Tile::NorthWestPipe), _) => { - ret.push(position.go(Direction::South)); - ret.push(position.go(Direction::East)); - } - (Some(Tile::NorthEastPipe), Direction::South) => {} // Nothing inside here - (Some(Tile::NorthEastPipe), _) => { - ret.push(position.go(Direction::South)); - ret.push(position.go(Direction::West)); - } - _ => {} - } - ret.into_iter() - } } impl Position { - pub fn direction_from(self, from: Self) -> Direction { - match ( - (self.row - from.row).signum(), - (self.col - from.col).signum(), - ) { - (0, 0) => Direction::None, - (0, -1) => Direction::East, - (0, 1) => Direction::West, - (-1, 0) => Direction::South, - (1, 0) => Direction::North, - _ => unreachable!(), - } - } - pub fn go(self, direction: Direction) -> Self { match direction { Direction::East => Self { @@ -319,24 +156,28 @@ impl Position { row: self.row + 1, col: self.col, }, - Direction::None => self, } } -} - -impl Direction { - pub fn cardinal() -> impl Iterator { - [Self::North, Self::South, Self::East, Self::West].into_iter() - } - pub fn rev(self) -> Self { - match self { - Self::North => Self::South, - Self::South => Self::North, - Self::East => Self::West, - Self::West => Self::East, - _ => self, - } + pub fn checked_go(self, direction: Direction) -> Option { + Some(match direction { + Direction::East => Self { + row: self.row, + col: self.col + 1, + }, + Direction::West => Self { + row: self.row, + col: self.col.checked_sub(1)?, + }, + Direction::North => Self { + row: self.row.checked_sub(1)?, + col: self.col, + }, + Direction::South => Self { + row: self.row + 1, + col: self.col, + }, + }) } } @@ -353,27 +194,55 @@ impl Tile { .into_iter() } - pub fn neighbors(self, position: Position) -> impl Iterator { + pub fn next_direction(self, dir: Direction) -> Direction { + match (self, dir) { + (Tile::HorizontalPipe, _) | (Tile::VerticalPipe, _) => dir, + (Tile::NorthEastPipe, Direction::South) => Direction::East, + (Tile::NorthEastPipe, Direction::West) => Direction::North, + (Tile::NorthWestPipe, Direction::South) => Direction::West, + (Tile::NorthWestPipe, Direction::East) => Direction::North, + (Tile::SouthEastPipe, Direction::North) => Direction::East, + (Tile::SouthEastPipe, Direction::West) => Direction::South, + (Tile::SouthWestPipe, Direction::North) => Direction::West, + (Tile::SouthWestPipe, Direction::East) => Direction::South, + _ => dir, + } + } + + pub fn neighbors(self, pos: Position) -> impl Iterator { let mut ret = ArrayVec::<[Position; 2]>::new(); + use Direction::*; match self { - Tile::HorizontalPipe => { - ret.extend([position.go(Direction::West), position.go(Direction::East)]) - } - Tile::VerticalPipe => { - ret.extend([position.go(Direction::North), position.go(Direction::South)]) - } - Tile::NorthEastPipe => { - ret.extend([position.go(Direction::North), position.go(Direction::East)]) - } - Tile::NorthWestPipe => { - ret.extend([position.go(Direction::North), position.go(Direction::West)]) - } - Tile::SouthEastPipe => { - ret.extend([position.go(Direction::South), position.go(Direction::East)]) - } - Tile::SouthWestPipe => { - ret.extend([position.go(Direction::South), position.go(Direction::West)]) - } + Tile::HorizontalPipe => ret.extend( + [pos.checked_go(West), pos.checked_go(East)] + .into_iter() + .flatten(), + ), + Tile::VerticalPipe => ret.extend( + [pos.checked_go(North), pos.checked_go(South)] + .into_iter() + .flatten(), + ), + Tile::NorthEastPipe => ret.extend( + [pos.checked_go(North), pos.checked_go(East)] + .into_iter() + .flatten(), + ), + Tile::NorthWestPipe => ret.extend( + [pos.checked_go(North), pos.checked_go(West)] + .into_iter() + .flatten(), + ), + Tile::SouthEastPipe => ret.extend( + [pos.checked_go(South), pos.checked_go(East)] + .into_iter() + .flatten(), + ), + Tile::SouthWestPipe => ret.extend( + [pos.checked_go(South), pos.checked_go(West)] + .into_iter() + .flatten(), + ), _ => {} } ret.into_iter() @@ -439,8 +308,8 @@ impl FromStr for Field { starting_position: starting_position .ok_or(ParseFieldError::NoStartingPosition) .map(|index| Position { - row: (index / cols) as isize, - col: (index % cols) as isize, + row: (index / cols), + col: (index % cols), })?, }; let starting_position = field.starting_position;