diff --git a/src/day12.rs b/src/day12.rs index 9411008..fdf8047 100644 --- a/src/day12.rs +++ b/src/day12.rs @@ -3,7 +3,7 @@ use crate::util::maps::*; const DISCOUNT: bool = true; pub fn day12() { - let mut plots: World = World::from_string(&crate::input(12)); + let mut plots: Map = Map::from_string(&crate::input(12)); let mut regions: Vec = vec![]; for (x, y) in plots.coordinates() { @@ -78,20 +78,20 @@ impl Border { struct Region { pub borders: Vec, - pub tiles: World, + pub tiles: Map, } impl Region { - fn new(x: usize, y: usize, plots: &World) -> Self { + fn new(x: usize, y: usize, plots: &Map) -> Self { let c = plots.get(x, y).unwrap(); - let mut tiles = World::::from_dimensions(plots.width(), plots.height(), false); + let mut tiles = Map::::from_dimensions(plots.width(), plots.height(), false); fn collect_plots( c: char, x: usize, y: usize, - plots: &World, - tiles: &mut World, + plots: &Map, + tiles: &mut Map, ) -> Vec<(usize, usize)> { if plots.get(x, y) != Some(c) || tiles.get(x, y) == Some(true) { return vec![]; diff --git a/src/day15.rs b/src/day15.rs index bd97625..5aa1f3b 100644 --- a/src/day15.rs +++ b/src/day15.rs @@ -40,11 +40,11 @@ pub fn day15() { } fn move_on_map( - map: &World, + map: &Map, x: usize, y: usize, direction: Direction, -) -> Option> { +) -> Option> { let cascading_move = || { let (nx, ny) = map.travel(x, y, direction)?; let mut downstream = move_on_map(map, nx, ny, direction)?; @@ -82,7 +82,7 @@ fn move_on_map( } } -fn double_wide_map(map: World) -> World { +fn double_wide_map(map: Map) -> Map { let new_map = map .iter_rows() .map(|row| { @@ -100,7 +100,7 @@ fn double_wide_map(map: World) -> World { .collect() }) .collect(); - World::from_2d_vec(new_map) + Map::from_2d_vec(new_map) } #[derive(Copy, Clone, Eq, PartialEq, Debug)] @@ -129,7 +129,7 @@ impl Display for Terrain { #[grammar = "grammars/day15.pest"] struct Day15Parser {} -fn parse(input: &str) -> (World, Vec) { +fn parse(input: &str) -> (Map, Vec) { let (map, directions) = Day15Parser::parse(Rule::input, input) .unwrap() .nth(0) @@ -142,7 +142,7 @@ fn parse(input: &str) -> (World, Vec) { (parse_map(map), parse_directions(directions)) } -fn parse_map(input: Pairs<'_, Rule>) -> World { +fn parse_map(input: Pairs<'_, Rule>) -> Map { let map_vec = input .map(Pair::<'_, Rule>::into_inner) .map(|row| { @@ -156,7 +156,7 @@ fn parse_map(input: Pairs<'_, Rule>) -> World { .collect() }) .collect(); - World::from_2d_vec(map_vec) + Map::from_2d_vec(map_vec) } fn parse_directions(input: Pairs<'_, Rule>) -> Vec { diff --git a/src/day16.rs b/src/day16.rs deleted file mode 100644 index 87e0d5d..0000000 --- a/src/day16.rs +++ /dev/null @@ -1,183 +0,0 @@ -use crate::util::maps::*; -use itertools::Itertools; -use std::collections::HashMap; - -pub fn day16() { - let input: World = World::from_string(&crate::input(16)); - let maze: World = input.map(|&c| c == '.' || c == 'S' || c == 'E'); - - let start: Transform = input - .enumerate() - .find(|(_, _, &c)| c == 'S') - .map(|(x, y, _)| Transform::new(x, y, Direction::East, &maze)) - .unwrap(); - let end: (usize, usize) = input - .enumerate() - .find(|(_, _, &c)| c == 'E') - .map(|(x, y, _)| (x, y)) - .unwrap(); - - let (cheapest_cost, paths) = cheapest_paths(start, end); - - let tiles_crossed: usize = paths - .iter() - .flatten() - .sorted_by_key(|p| (p.x, p.y)) - .dedup_by(|a, b| a.x == b.x && a.y == b.y) - .count(); - - println!("Found {} shortest paths.", paths.len()); - println!("Cheapest Path to End: {}", cheapest_cost); - println!("Tiles Crossed: {}", tiles_crossed); -} - -fn cheapest_paths(start: Transform, end: (usize, usize)) -> (u64, Vec>) { - let paths: Vec<(Vec, u64)> = all_paths(start, end) - .into_iter() - // path -> (path, cost) - .map(|path| { - let cost = path.windows(2).into_iter().fold(0, |acc, w| { - acc + if w[0].direction == w[1].direction { - 1 - } else { - 1000 - } - }); - (path, cost) - }) - // keep only the paths tied for cheapest - .min_set_by_key(|(_, cost)| *cost); - let cheapest_cost = paths[0].1; - // (path, cost) -> path - let paths = paths.into_iter().map(|(path, _)| path).collect(); - (cheapest_cost, paths) -} - -fn all_paths(start: Transform, end: (usize, usize)) -> Vec> { - let costs = maze_costs(start); - - fn collect_paths<'a, 'b>( - costs: &'b HashMap, u64>, - p: Transform<'a>, - ) -> Vec>> { - let cost_p = costs[&p]; - - // cost 0 means this is the start - if cost_p == 0 { - return vec![vec![p]]; - }; - // otherwise we look at all ways that we might get to p - p.comes_from() - .into_iter() - // no cost stored means that x is a silly place to ever come from (a dead end) - .filter(|x| costs.contains_key(x)) - // if cost_x < cost_p, x most be closer to the start than p is - // otherwise, we don't want to check x (that would be anti-progress) - .filter(|x| costs[x] < cost_p) - .flat_map(|x| { - // now we recur, getting all paths from start to x - let mut paths = collect_paths(costs, x); - // and add ourself to the paths, then send the paths up the chain - paths.iter_mut().for_each(|path| path.push(p)); - paths - }) - .collect() - } - - // every facing direction at the end may have a different cost - let endpoints = costs.keys().filter(|t| t.x == end.0 && t.y == end.1); - endpoints - .into_iter() - .flat_map(|&endpoint| collect_paths(&costs, endpoint)) - .collect() -} - -fn maze_costs(start: Transform) -> HashMap { - fn calculate_costs<'a, 'b>(costs: &'b mut HashMap, u64>, p: Transform<'a>) { - // cost to any x adjacent to p is the cost to get to p + the cost to travel p -> x - // and if that's cheaper than any previous way we've found, repeat for each x as the new p - for (travel_cost, adjacent) in p.goes_to() { - let total_cost = costs[&p] + travel_cost; - let existing_cost = costs.get(&adjacent).copied().unwrap_or(u64::MAX); - if total_cost < existing_cost { - costs.insert(adjacent, total_cost); - calculate_costs(costs, adjacent); - } - } - } - - let mut costs = HashMap::new(); - costs.insert(start, 0); - calculate_costs(&mut costs, start); - costs -} - -#[derive(Copy, Clone)] -struct Transform<'a> { - x: usize, - y: usize, - direction: Direction, - map: &'a World, -} - -impl<'a> Transform<'a> { - fn new(x: usize, y: usize, direction: Direction, map: &'a World) -> Self { - Self { - x, - y, - direction, - map, - } - } - - fn goes_to(self) -> Vec<(u64, Transform<'a>)> { - let mut ret: Vec<(u64, Transform)> = vec![ - self.direction.clockwise(), - self.direction.counterclockwise(), - ] - .into_iter() - .filter(|d| self.map.travel_get(self.x, self.y, *d) == Some(true)) - .map(|d| (1000, Self::new(self.x, self.y, d, self.map))) - .collect(); - if let Some((nx, ny, true)) = self.map.travel_and_get(self.x, self.y, self.direction) { - ret.push((1, Self::new(nx, ny, self.direction, self.map))); - } - ret - } - - fn comes_from(self) -> Vec> { - let mut ret: Vec> = vec![ - self.direction.clockwise(), - self.direction.counterclockwise(), - ] - .into_iter() - .map(|d| Self::new(self.x, self.y, d, self.map)) - .collect(); - let backward = self - .map - .travel_and_get(self.x, self.y, self.direction.opposite()); - if let Some((nx, ny, true)) = backward { - ret.push(Self::new(nx, ny, self.direction, self.map)); - } - ret - } -} - -impl<'a> PartialEq for Transform<'a> { - fn eq(&self, other: &Self) -> bool { - std::ptr::eq(self.map, other.map) - && self.x == other.x - && self.y == other.y - && self.direction == other.direction - } -} - -impl<'a> Eq for Transform<'a> {} - -impl<'a> std::hash::Hash for Transform<'a> { - fn hash(&self, state: &mut H) { - self.x.hash(state); - self.y.hash(state); - self.direction.hash(state); - } -} diff --git a/src/main.rs b/src/main.rs index ce7a2f1..8f686d5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,12 +28,10 @@ mod day12; mod day13; #[allow(dead_code)] mod day14; -#[allow(dead_code)] mod day15; -mod day16; fn main() { - day16::day16(); + day15::day15(); } pub fn input(day: u8) -> String { diff --git a/src/util/maps.rs b/src/util/maps.rs index 19ef915..97e4d9c 100644 --- a/src/util/maps.rs +++ b/src/util/maps.rs @@ -2,7 +2,7 @@ use std::fmt::Display; use itertools::Itertools; -#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Debug)] pub enum Direction { North, South, @@ -27,37 +27,19 @@ impl Direction { pub fn is_parallel(self, other: Self) -> bool { self == other || self == other.opposite() } - - pub fn clockwise(self) -> Self { - match self { - Self::North => Self::East, - Self::East => Self::South, - Self::South => Self::West, - Self::West => Self::North, - } - } - - pub fn counterclockwise(self) -> Self { - match self { - Self::North => Self::West, - Self::West => Self::South, - Self::South => Self::East, - Self::East => Self::North, - } - } } #[derive(Clone)] -pub struct World +pub struct Map where T: Copy, { pub map: Vec>, } -impl World { +impl Map { pub fn from_string(string: &str) -> Self { - World::from_2d_vec( + Map::from_2d_vec( string .lines() .filter(|line| !line.is_empty()) @@ -67,7 +49,7 @@ impl World { } } -impl World +impl Map where T: Copy, { @@ -152,14 +134,9 @@ where pub fn enumerate(&self) -> impl Iterator { self.coordinates().map(|(x, y)| (x, y, &self.map[y][x])) } - - pub fn map U>(&self, f: F) -> World { - let v = self.map.iter().map(|row| row.iter().map(|e| f(e)).collect()).collect(); - World::::from_2d_vec(v) - } } -impl Display for World +impl Display for Map where T: Copy + Display, {