diff --git a/day12/src/lib.rs b/day12/src/lib.rs new file mode 100644 index 0000000..ec502b3 --- /dev/null +++ b/day12/src/lib.rs @@ -0,0 +1,287 @@ +mod node; +use core::fmt; +use node::Node; + +use aoc_shared::enums::Direction; +use aoc_shared::grid::Grid as BaseGrid; +use aoc_shared::impl_grid_newtype; + +#[derive(Debug, Copy, Clone, PartialEq)] +enum CellType { + Start, + End, + Waypoint(u8), +} + +#[derive(Debug, Clone)] +struct Cell { + kind: CellType, + idx: usize, + coord: (usize, usize), +} + +impl Cell { + pub fn new(kind: CellType, idx: usize, coord: (usize, usize)) -> Self { + Cell { kind, idx, coord } + } + + pub fn get_height(&self) -> u8 { + match self.kind { + CellType::Start => 0, + CellType::End => 25, + CellType::Waypoint(c) => c, + } + } +} + +// ---------------------------------------------------------------------------- + +pub struct Grid(BaseGrid); +impl_grid_newtype!(Grid, BaseGrid, Cell); + +impl Grid { + pub fn from_file_str(file_str: &str) -> Self { + let first_line = file_str.lines().next().unwrap(); + let width = first_line.len(); + + let mut grid = Grid::new(width); + let mut idx = 0usize; + + for c in file_str.chars() { + let kind = match c { + 'S' => CellType::Start, + 'E' => CellType::End, + 'a'..='z' => CellType::Waypoint(c as u8 - b'a'), + '\r' | '\n' => continue, + _ => panic!("Invalid character: {c}"), + }; + + let (x, y) = (idx % width, idx / width); + let cell = Cell::new(kind, idx, (x, y)); + + grid.vec.push(cell); + + idx += 1; + } + + grid + } + + fn find_pos(&self, value: CellType) -> Option { + self.vec.iter().position(|item| item.kind == value) + } + + pub fn print(&self) { + for r in 0usize..self.num_rows() { + let range = self.row_first_idx(r)..=self.row_last_idx(r); + let line: String = self.vec[range] + .iter() + .map(|n| (n.get_height() + b'a') as char) + .collect(); + + println!("{}", line); + } + } + + fn get_index_for_move(&self, from: usize, dir: Direction) -> Option { + let (mut x, mut y) = self.idx_xy(from); + + match dir { + Direction::Up => { + if y >= 1 { + y -= 1; + } else { + return None; + } + } + Direction::Down => { + if y < self.num_rows() - 1 { + y += 1; + } else { + return None; + } + } + Direction::Left => { + if x >= 1 { + x -= 1; + } else { + return None; + } + } + Direction::Right => { + if x < self.num_cols() - 1 { + x += 1; + } else { + return None; + } + } + }; + + Some(self.xy_idx(x, y)) + } + + fn is_valid_move(&self, start: usize, end: usize) -> bool { + // Is the item within the grid? + let start_char = self.get(start); + let end_char = self.get(end); + if start_char.is_none() || end_char.is_none() { + return false; + } + + let start_char = start_char.unwrap(); + let end_char = end_char.unwrap(); + let start_elevation = start_char.get_height(); + let end_elevation = end_char.get_height(); + if (end_elevation < start_elevation) || end_elevation.abs_diff(start_elevation) > 1 { + return false; + } + + let ((start_x, start_y), (end_x, end_y)) = (start_char.coord, end_char.coord); + let x_diff = end_x.abs_diff(start_x); + let y_diff = end_y.abs_diff(start_y); + + // Have we moved 0 or 1 in a cardinal direction? + matches!((x_diff, y_diff), (0, 0) | (0, 1) | (1, 0)) + } + + fn find_moves(&self, start: usize) -> Vec { + [ + Direction::Up, + Direction::Down, + Direction::Left, + Direction::Right, + ] + .into_iter() + .filter_map(|d| self.get_index_for_move(start, d)) + .collect() + } + + fn find_valid_moves(&self, start: usize) -> Vec { + self.find_moves(start) + .into_iter() + .filter(|m| self.is_valid_move(start, *m)) + .collect() + } + + fn has_valid_neighbor(&self, idx: usize) -> bool { + !self.find_valid_moves(idx).is_empty() + } +} + +impl fmt::Debug for Grid { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for r in 0usize..self.num_rows() { + let range = self.row_first_idx(r)..=self.row_last_idx(r); + let line: String = self.vec[range] + .iter() + .map(|n| (n.get_height() + b'a') as char) + .collect(); + + writeln!(f, "{}", line)?; + } + + Ok(()) + } +} + +// ---------------------------------------------------------------------------- + +#[derive(Debug)] +pub struct Pathfinder { + start_idx: usize, + end_idx: usize, + grid: Grid, + tree: Node, +} + +impl Pathfinder { + pub fn from_file_str(file_str: &str) -> Self { + let mut pf = Pathfinder { + start_idx: 0, + end_idx: 0, + grid: Grid::from_file_str(file_str), + tree: Node::default(), + }; + + let start = pf.grid.find_pos(CellType::Start).unwrap(); + let end = pf.grid.find_pos(CellType::End).unwrap(); + + pf.start_idx = start; + pf.end_idx = end; + pf.tree.idx = start; + + pf + } + + fn add_children(&mut self, node: &mut Node, idx: usize) { + let possible_moves = self.grid.find_valid_moves(idx); + + if possible_moves.is_empty() { + return; + } + + for m in possible_moves { + if node.contains(m) { + continue; + } + + let n = node.add_child(m); + if m != self.end_idx { + self.add_children(n, m); + } else { + break; + } + } + } + + fn build_tree(&mut self) { + let mut tree = { self.tree.clone() }; + let idx = { self.start_idx }; + + self.add_children(&mut tree, idx); + self.tree = tree; + } + + fn get_paths(&self) -> impl Iterator { + self.tree + .get_leaves() + .into_iter() + .filter(|n| n.idx == self.end_idx) + } + + pub fn find_shortest_path(&mut self) -> &Node { + self.build_tree(); + + self.get_paths() + .min_by(|a, b| a.get_len().cmp(&b.get_len())) + .unwrap() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn get_test_data() -> &'static str { + include_str!("test-input.txt") + } + + fn get_finder() -> Pathfinder { + Pathfinder::from_file_str(get_test_data()) + } + + #[test] + fn find_valid_moves() { + let finder = get_finder(); + + assert_eq!(finder.grid.find_valid_moves(finder.start_idx), vec![8, 1]); + assert_eq!(finder.grid.find_valid_moves(8), vec![0, 16, 9]); + } + + #[test] + fn find_shortest_path() { + let mut finder = get_finder(); + let shortest = finder.find_shortest_path(); + assert_eq!(shortest.get_len(), 31); + } +} diff --git a/day12/src/main.rs b/day12/src/main.rs index 7ba79af..6093f55 100644 --- a/day12/src/main.rs +++ b/day12/src/main.rs @@ -1,281 +1,7 @@ -mod node; -use node::Node; - -use aoc_shared::enums::Direction; -use aoc_shared::grid::Grid as BaseGrid; -use aoc_shared::impl_grid_newtype; - -#[derive(Debug, Copy, Clone, PartialEq)] -enum CellType { - Start, - End, - Waypoint(u8), -} - -#[derive(Debug, Clone)] -struct Cell { - kind: CellType, - idx: usize, - coord: (usize, usize), -} - -impl Cell { - pub fn new(kind: CellType, idx: usize, coord: (usize, usize)) -> Self { - Cell { kind, idx, coord } - } - - pub fn get_height(&self) -> u8 { - match self.kind { - CellType::Start => 0, - CellType::End => 25, - CellType::Waypoint(c) => c, - } - } -} - -// ---------------------------------------------------------------------------- - -#[derive(Debug)] -pub struct Grid(BaseGrid); -impl_grid_newtype!(Grid, BaseGrid, Cell); - -impl Grid { - pub fn from_file_str(file_str: &str) -> Self { - let first_line = file_str.lines().next().unwrap(); - let width = first_line.len(); - - let mut grid = Grid::new(width); - let mut idx = 0usize; - - for c in file_str.chars() { - let kind = match c { - 'S' => CellType::Start, - 'E' => CellType::End, - 'a'..='z' => CellType::Waypoint(c as u8 - b'a'), - '\r' | '\n' => continue, - _ => panic!("Invalid character: {c}"), - }; - - let (x, y) = (idx % width, idx / width); - let cell = Cell::new(kind, idx, (x, y)); - - grid.vec.push(cell); - - idx += 1; - } - - grid - } - - fn find_pos(&self, value: CellType) -> Option { - self.vec.iter().position(|item| item.kind == value) - } - - pub fn print(&self) { - for r in 0usize..self.num_rows() { - let range = self.row_first_idx(r)..=self.row_last_idx(r); - let line: String = self.vec[range] - .iter() - .map(|n| (n.get_height() + b'a') as char) - .collect(); - - println!("{}", line); - } - } - - fn get_index_for_move(&self, from: usize, dir: Direction) -> Option { - let (mut x, mut y) = self.idx_xy(from); - - match dir { - Direction::Up => { - if y >= 1 { - y -= 1; - } else { - return None; - } - } - Direction::Down => { - if y < self.num_rows() - 1 { - y += 1; - } else { - return None; - } - } - Direction::Left => { - if x >= 1 { - x -= 1; - } else { - return None; - } - } - Direction::Right => { - if x < self.num_cols() - 1 { - x += 1; - } else { - return None; - } - } - }; - - Some(self.xy_idx(x, y)) - } - - fn is_valid_move(&self, start: usize, end: usize) -> bool { - // Is the item within the grid? - let start_char = self.get(start); - let end_char = self.get(end); - if start_char.is_none() || end_char.is_none() { - return false; - } - - let start_char = start_char.unwrap(); - let end_char = end_char.unwrap(); - let start_elevation = start_char.get_height(); - let end_elevation = end_char.get_height(); - if (end_elevation < start_elevation) || end_elevation.abs_diff(start_elevation) > 1 { - return false; - } - - let ((start_x, start_y), (end_x, end_y)) = (start_char.coord, end_char.coord); - let x_diff = end_x.abs_diff(start_x); - let y_diff = end_y.abs_diff(start_y); - - // Have we moved 0 or 1 in a cardinal direction? - matches!((x_diff, y_diff), (0, 0) | (0, 1) | (1, 0)) - } - - fn find_moves(&self, start: usize) -> Vec { - [ - Direction::Up, - Direction::Down, - Direction::Left, - Direction::Right, - ] - .into_iter() - .filter_map(|d| self.get_index_for_move(start, d)) - .collect() - } - - fn find_valid_moves(&self, start: usize) -> Vec { - self.find_moves(start) - .into_iter() - .filter(|m| self.is_valid_move(start, *m)) - .collect() - } - - fn has_valid_neighbor(&self, idx: usize) -> bool { - !self.find_valid_moves(idx).is_empty() - } -} - -// ---------------------------------------------------------------------------- - -#[derive(Debug)] -pub struct Pathfinder { - start_idx: usize, - end_idx: usize, - grid: Grid, - tree: Node, -} - -impl Pathfinder { - pub fn from_file_str(file_str: &str) -> Self { - let mut pf = Pathfinder { - start_idx: 0, - end_idx: 0, - grid: Grid::from_file_str(file_str), - tree: Node::default(), - }; - - let start = pf.grid.find_pos(CellType::Start).unwrap(); - let end = pf.grid.find_pos(CellType::End).unwrap(); - - pf.start_idx = start; - pf.end_idx = end; - pf.tree.idx = start; - - pf - } - - fn add_children(&mut self, node: &mut Node, idx: usize) { - let possible_moves = self.grid.find_valid_moves(idx); - - if possible_moves.is_empty() { - return; - } - - for m in possible_moves { - if node.contains(m) { - continue; - } - - let n = node.add_child(m); - if m != self.end_idx { - self.add_children(n, m); - } else { - break; - } - } - } - - fn build_tree(&mut self) { - let mut tree = { self.tree.clone() }; - let idx = { self.start_idx }; - - self.add_children(&mut tree, idx); - self.tree = tree; - } - - fn get_paths(&self) -> impl Iterator { - self.tree - .get_leaves() - .into_iter() - .filter(|n| n.idx == self.end_idx) - } - - pub fn find_shortest_path(&mut self) -> &Node { - self.build_tree(); - - self.get_paths() - .min_by(|a, b| a.get_len().cmp(&b.get_len())) - .unwrap() - } -} - -// ---------------------------------------------------------------------------- - fn main() { let file_str = include_str!("input.txt"); - let mut finder = Pathfinder::from_file_str(file_str); + let mut finder = ::day12::Pathfinder::from_file_str(file_str); let shortest_path = finder.find_shortest_path(); println!("Part 1: Fewest steps: {}", shortest_path.get_len()); } - -#[cfg(test)] -mod tests { - use super::*; - - fn get_test_data() -> &'static str { - include_str!("test-input.txt") - } - - fn get_finder() -> Pathfinder { - Pathfinder::from_file_str(get_test_data()) - } - - #[test] - fn find_valid_moves() { - let finder = get_finder(); - - assert_eq!(finder.grid.find_valid_moves(finder.start_idx), vec![8, 1]); - assert_eq!(finder.grid.find_valid_moves(8), vec![0, 16, 9]); - } - - #[test] - fn find_shortest_path() { - let mut finder = get_finder(); - let shortest = finder.find_shortest_path(); - assert_eq!(shortest.get_len(), 31); - } -} diff --git a/day12/src/node.rs b/day12/src/node.rs index faae32a..32cabce 100644 --- a/day12/src/node.rs +++ b/day12/src/node.rs @@ -20,9 +20,7 @@ impl Node { self.append(child); - self.children - .last_mut() - .unwrap() + self.children.last_mut().unwrap() } fn append(&mut self, node: Node) -> &mut Self { @@ -50,9 +48,7 @@ impl Node { let mut leaves = Vec::new(); - let children = self - .children - .iter(); + let children = self.children.iter(); for child in children { let mut child_leaves = child.get_leaves(); @@ -65,4 +61,4 @@ impl Node { pub fn get_len(&self) -> usize { self.parents.len() } -} \ No newline at end of file +}