diff --git a/17/Cargo.lock b/17/Cargo.lock index c67b525..1b61b13 100644 --- a/17/Cargo.lock +++ b/17/Cargo.lock @@ -2,11 +2,18 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "day17" version = "0.1.0" dependencies = [ "petgraph", + "test-case", ] [[package]] @@ -46,3 +53,71 @@ dependencies = [ "fixedbitset", "indexmap", ] + +[[package]] +name = "proc-macro2" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "2.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "test-case" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2550dd13afcd286853192af8601920d959b14c401fcece38071d53bf0768a8" +dependencies = [ + "test-case-macros", +] + +[[package]] +name = "test-case-core" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcb7fd841cd518e279be3d5a3eb0636409487998a4aff22f3de87b81e88384f" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "test-case-macros" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "test-case-core", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" diff --git a/17/Cargo.toml b/17/Cargo.toml index 82a66a9..f6d268c 100644 --- a/17/Cargo.toml +++ b/17/Cargo.toml @@ -7,3 +7,4 @@ edition = "2021" [dependencies] petgraph = "0.6.4" +test-case = "3.3.1" diff --git a/17/src/main.rs b/17/src/main.rs index 54b9e46..962c22d 100644 --- a/17/src/main.rs +++ b/17/src/main.rs @@ -150,12 +150,18 @@ impl<'a> WalkCost<'a> { // if cur_move.weight >= self.cost_to[cur_move.new_pos.1][cur_move.new_pos.0] { // continue; // } - if let Some(last_weight) = self.cost_from[cur_move.new_pos.1][cur_move.new_pos.0].get(&(*cur_move.dir, cur_move.consecutive)) { + if let Some(last_weight) = self.cost_from[cur_move.new_pos.1][cur_move.new_pos.0] + .get(&(*cur_move.dir, cur_move.consecutive)) + { if cur_move.weight < *last_weight { - self.cost_from[cur_move.new_pos.1][cur_move.new_pos.0].insert((*cur_move.dir, cur_move.consecutive), cur_move.weight); - } else { continue; }// visited before at lower cost } + self.cost_from[cur_move.new_pos.1][cur_move.new_pos.0] + .insert((*cur_move.dir, cur_move.consecutive), cur_move.weight); + } else { + continue; + } // visited before at lower cost } } else { - self.cost_from[cur_move.new_pos.1][cur_move.new_pos.0].insert((*cur_move.dir, cur_move.consecutive), cur_move.weight); + self.cost_from[cur_move.new_pos.1][cur_move.new_pos.0] + .insert((*cur_move.dir, cur_move.consecutive), cur_move.weight); } // println!("state at {:?}: {:?}", cur_move, self.cost_from[cur_move.new_pos.1][cur_move.new_pos.0]); @@ -175,17 +181,21 @@ impl<'a> WalkCost<'a> { }) }) .filter(|m| m.consecutive != 4 && *m.dir != cur_move.dir.opposite()) - .filter(|m| { - m.weight < *self.cost_from[m.new_pos.1][m.new_pos.0].get(&(*m.dir, m.consecutive)).unwrap_or(&u64::MAX) - }) + .filter(|m| { + m.weight + < *self.cost_from[m.new_pos.1][m.new_pos.0] + .get(&(*m.dir, m.consecutive)) + .unwrap_or(&u64::MAX) + }) }); // valid positions - // println!("valid moves with {:?}", cur_move); + // println!("valid moves with {:?}", cur_move); for next_move in valid_moves { // println!(" {:?}", next_move); unvisited_next_moves.push(next_move); } } } + fn shortest_path_to(&self, to: Position) -> Vec<(Position, Direction)> { let mut path = Vec::new(); let mut cur_pos = to; @@ -219,19 +229,140 @@ impl<'a> WalkCost<'a> { } } -impl<'a> Display for WalkCost<'a> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - // for y in 0..self.cost_to.len() { - // for cost in &self.cost_to[y] { - // f.write_fmt(format_args!("{:3} ", cost))?; - // } - // f.write_str(" ")?; - // for input in &self.map.map[y] { - // f.write_fmt(format_args!("{:3} ", input))?; - // } - // writeln!(f)?; - // } - Ok(()) +struct WalkCost2<'a> { + start: Position, + cost_from: Vec>>, + map: &'a CityMap, +} + +impl<'a> WalkCost2<'a> { + fn from_map(map: &'a CityMap, start: Position) -> Self { + Self { + map, + start, + cost_from: map + .map + .iter() + .map(|row| repeat(HashMap::new()).take(row.len()).collect()) + .collect(), + } + } + fn compute(&mut self, to: Position) { + println!("computing to: {:?}", to); + let mut unvisited_next_moves: BinaryHeap = BinaryHeap::new(); + let valid_start_moves: Vec = Direction::all() + .iter() + .filter_map(|dir| { + self.map.offset_pos(self.start, dir).and_then(|pos| { + Some(Move { + new_pos: pos, + dir, + consecutive: 1, + weight: self.map.map[pos.1][pos.0], + }) + }) + }) // valid positions + .collect(); + + for m in valid_start_moves { + unvisited_next_moves.push(m); + } + + while let Some(cur_move) = unvisited_next_moves.pop() { + // we've been here already at a lower cost + // if cur_move.weight >= self.cost_to[cur_move.new_pos.1][cur_move.new_pos.0] { + // continue; + // } + if let Some(last_weight) = self.cost_from[cur_move.new_pos.1][cur_move.new_pos.0] + .get(&(*cur_move.dir, cur_move.consecutive)) + { + if cur_move.weight < *last_weight { + self.cost_from[cur_move.new_pos.1][cur_move.new_pos.0] + .insert((*cur_move.dir, cur_move.consecutive), cur_move.weight); + // println!("move {:?} inserted {:?}", cur_move, (*cur_move.dir, cur_move.consecutive)); + } else { + continue; + } // visited before at lower cost } + } else { + // println!("move {:?} inserted {:?}", cur_move, (*cur_move.dir, cur_move.consecutive)); + self.cost_from[cur_move.new_pos.1][cur_move.new_pos.0] + .insert((*cur_move.dir, cur_move.consecutive), cur_move.weight); + } + + if cur_move.new_pos == to { + // println!("reached end pos {:?} via {:?}", to, cur_move); + continue; + } + let valid_moves = Direction::all().iter().filter_map(|dir| { + self.map + .offset_pos(cur_move.new_pos, dir) + .and_then(|new_pos| { + Some(Move { + new_pos, + dir, + consecutive: if cur_move.dir == dir { + cur_move.consecutive + 1 + } else { + 1 + }, + weight: cur_move.weight + self.map.map[new_pos.1][new_pos.0], + }) + }) + .filter(|m| *m.dir != cur_move.dir.opposite()) + .filter(|m| { + if m.dir == cur_move.dir { + m.consecutive < 11 + } else { + cur_move.consecutive >= 4 + } + }) + .filter(|m| m.new_pos != to || m.consecutive >= 4) + .filter(|m| { + m.weight + < *self.cost_from[m.new_pos.1][m.new_pos.0] + .get(&(*m.dir, m.consecutive)) + .unwrap_or(&u64::MAX) + }) + }); // valid positions + // println!("valid moves with {:?}", cur_move); + for next_move in valid_moves { + // println!(" {:?}", next_move); + unvisited_next_moves.push(next_move); + } + } + } + + + fn shortest_path_to(&self, to: Position) -> Vec<(Position, Direction)> { + let mut path = Vec::new(); + let mut cur_pos = to; + // start at the end, walk backwards + while cur_pos != self.start { + let (m, val) = self.cost_from[cur_pos.1][cur_pos.0].iter().min_by(|a, b| a.1.cmp(b.1)).unwrap(); + path.push((cur_pos, m.0)); + cur_pos = self.map.offset_pos(cur_pos, &m.0.opposite()).unwrap(); + } + path + } + fn print_shortest_path(&self, to: Position) { + let path = self.shortest_path_to(to); + let map: HashMap<_, _, RandomState> = HashMap::from_iter(path.into_iter()); + for y in 0..self.cost_from.len() { + for x in 0..self.map.map[y].len() { + if let Some(to_dir) = map.get(&(x, y)) { + let c = match to_dir { + Direction::Left => '<', + Direction::Right => '>', + Direction::Up => '^', + Direction::Down => 'v', + }; + print!("{}", c); + } else { + print!("{}", self.map.map[y][x]); + } + } + println!(); + } } } @@ -252,21 +383,35 @@ fn problem1(input: Lines) -> u64 { let mut costs = WalkCost::from_map(&map, (0, 0)); costs.compute(); - println!("{}", costs); + // println!("{}", costs); // costs.print_shortest_path((costs.cost_to[0].len() - 1, costs.cost_to.len() - 1)); - *costs.cost_from[costs.cost_from.len()-1][costs.cost_from[0].len() - 1].values().min().unwrap() + *costs.cost_from[costs.cost_from.len() - 1][costs.cost_from[0].len() - 1] + .values() + .min() + .unwrap() } // PROBLEM 2 solution fn problem2(input: Lines) -> u64 { - 0 + let map = CityMap::from(input); + let mut costs = WalkCost2::from_map(&map, (0, 0)); + costs.compute((map.map[0].len() - 1, map.map.len() - 1)); + + // println!("{}", costs); + costs.print_shortest_path((costs.cost_from[0].len() - 1, costs.cost_from.len() - 1)); + + *costs.cost_from[costs.cost_from.len() - 1][costs.cost_from[0].len() - 1] + .values() + .min() + .unwrap() } #[cfg(test)] mod tests { use crate::*; use std::io::Cursor; + use test_case::test_case; const EXAMPLE: &str = &"2413432311323 3215453535623 @@ -280,8 +425,13 @@ mod tests { 4564679986453 1224686865563 2546548887735 -4322674655533 -"; +4322674655533"; + + const EXAMPLE2: &str = &"111111111111 +999999999991 +999999999991 +999999999991 +999999999991"; #[test] fn problem1_example() { @@ -289,9 +439,10 @@ mod tests { assert_eq!(problem1(c.lines()), 102); } - #[test] - fn problem2_example() { - let c = Cursor::new(EXAMPLE); - assert_eq!(problem2(c.lines()), 0); + #[test_case(EXAMPLE, 94)] + #[test_case(EXAMPLE2, 71)] + fn problem2_example(example: &str, expect: u64) { + let c = Cursor::new(example); + assert_eq!(problem2(c.lines()), expect); } }