diff --git a/Cargo.lock b/Cargo.lock index fd8f458..7bda69b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,7 @@ version = "0.1.0" dependencies = [ "automod", "num", + "pathfinding", "rayon", "regex", "scan_fmt", @@ -79,12 +80,61 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "deprecate-until" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a3767f826efbbe5a5ae093920b58b43b01734202be697e1354914e862e8e704" +dependencies = [ + "proc-macro2", + "quote", + "semver", + "syn", +] + [[package]] name = "either" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "indexmap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "integer-sqrt" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276ec31bcb4a9ee45f58bec6f9ec700ae4cf4f4f8f2fa7e06cb406bd5ffdd770" +dependencies = [ + "num-traits", +] + [[package]] name = "itoa" version = "1.0.9" @@ -182,6 +232,21 @@ dependencies = [ "autocfg", ] +[[package]] +name = "pathfinding" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ea07a6e677e47d6a84724d4fdf88b1e37fcb49ac94e236d7caeefd8fee75c8a" +dependencies = [ + "deprecate-until", + "fixedbitset", + "indexmap", + "integer-sqrt", + "num-traits", + "rustc-hash", + "thiserror", +] + [[package]] name = "proc-macro2" version = "1.0.70" @@ -249,6 +314,12 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "ryu" version = "1.0.15" @@ -270,6 +341,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "semver" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" + [[package]] name = "serde" version = "1.0.193" @@ -312,6 +389,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "thiserror" +version = "1.0.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "unicode-ident" version = "1.0.12" diff --git a/Cargo.toml b/Cargo.toml index 82f7242..aedb12e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ edition = "2021" [dependencies] automod = "1.0.13" num = "0.4.1" +pathfinding = "4.6.0" rayon = "1.8.0" regex = "1.10.2" scan_fmt = "0.2.6" diff --git a/res/17/example b/res/17/example index e69de29..3c85086 100644 --- a/res/17/example +++ b/res/17/example @@ -0,0 +1,13 @@ +2413432311323 +3215453535623 +3255245654254 +3446585845452 +4546657867536 +1438598798454 +4457876987766 +3637877979653 +4654967986887 +4564679986453 +1224686865563 +2546548887735 +4322674655533 \ No newline at end of file diff --git a/src/days/d17.rs b/src/days/d17.rs index 64bf4b1..973e0a2 100644 --- a/src/days/d17.rs +++ b/src/days/d17.rs @@ -1,16 +1,328 @@ -use std::fs; +use std::{ + cmp::{Ordering, Reverse}, + collections::{BTreeMap, BinaryHeap, HashMap}, + fs, +}; pub fn solve() { let path = "res/17/example"; - let mut _contents = fs::read_to_string(path).expect("I/O error, wrong path?"); + let contents = fs::read_to_string(path).expect("I/O error, wrong path?"); //let contents = BufReader::new(fs::File::open(path).expect("I/O error, wrong path?")); - let result: usize = 0; + let grid: Vec> = contents + .lines() + .map(|line| { + line.chars() + .map(|c| c.to_digit(10).unwrap()) + .collect::>() + }) + .collect(); + let start = (0, 0); + let target = (grid.len() - 1, grid[0].len() - 1); + + let results = dijkstra2(&grid, start); + + let mut pri = grid.clone(); + let mut curr = (grid.len() - 1, grid[0].len() - 1); + while curr != (0, 0) { + pri[curr.0][curr.1] = 0; + curr = results.get(&curr).unwrap().unwrap().0; + } + for row in pri { + for num in row { + if num == 0 { + print!("#") + } else { + print!("{}", num) + } + } + println!() + } + println!(); + + let result = results.get(&target).unwrap().unwrap().1; println!("Result 1: {result}"); let result: usize = 0; println!("Result 2: {result}"); } + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +struct State { + cost: usize, + position: (usize, usize), +} + +impl Ord for State { + fn cmp(&self, other: &Self) -> Ordering { + other + .cost + .cmp(&self.cost) + .then_with(|| self.position.cmp(&other.position)) + } +} + +impl PartialOrd for State { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +#[derive(Debug)] +struct Edge { + node: (usize, usize), + cost: usize, +} +/* +fn dijkstra(grid: &Vec>, start: (usize, usize), goal: (usize, usize)) -> Option { + let mut dist: HashMap<(usize, usize), usize> = HashMap::new(); + let mut visited: HashMap<(usize, usize), bool> = HashMap::new(); + let mut prev: HashMap<(usize, usize), (usize, usize)> = HashMap::new(); + for x in 0..grid.len() { + for y in 0..grid[0].len() { + dist.insert((x, y), usize::MAX); + visited.insert((x, y), false); + } + } + + dist.insert(start, 0); + prev.insert(start, start); + + let mut heap = BinaryHeap::new(); + + heap.push(State { + cost: 0, + position: start, + }); + + while let Some(State { cost, position }) = heap.pop() { + if position == goal { + let mut pri = grid.clone(); + let mut curr = (grid.len() - 1, grid[0].len() - 1); + while curr != (0, 0) { + pri[curr.0][curr.1] = 0; + curr = *prev.get(&curr).unwrap(); + } + for row in pri { + for num in row { + if num == 0 { + print!("#") + } else { + print!("{}", num) + } + } + println!() + } + println!(); + for x in 0..grid.len() { + for y in 0..grid[0].len() { + if dist.contains_key(&(x, y)) { + print!("{}", format!(" {:04}", dist.get(&(x, y)).unwrap())) + } else { + print!(" ###") + } + } + println!() + } + return Some(cost); + } + + if cost > *dist.get(&position)? { + continue; + } + + let is_max_in_dir = max_in_dir(position, &prev); + let adj_list: Vec = + get_adjacent_positions(position, is_max_in_dir, *prev.get(&position).unwrap()) + .iter() + .filter(|(x, y)| *x < grid.len() && *y < grid[0].len()) + .map(|pos| Edge { + node: *pos, + cost: grid[pos.0][pos.1] as usize, + }) + .collect(); + + //println!("pos: {position:?}, adj: {:?}", &adj_list); + for edge in adj_list { + let next = State { + cost: cost + edge.cost, + position: edge.node, + }; + + if next.cost < *dist.get(&next.position)? { + heap.push(next); + + *dist.get_mut(&next.position)? = next.cost; + prev.insert(next.position, position); + } + } + } + None +} +*/ +// +// returns a map that for each reachable vertex associates the distance and the predecessor +// since the start has no predecessor but is reachable, map[start] will be None +pub fn dijkstra2( + graph: &Vec>, + start: (usize, usize), +) -> BTreeMap<(usize, usize), Option<((usize, usize), u32)>> { + let mut ans = BTreeMap::new(); + let mut prio = BinaryHeap::new(); + + // start is the special case that doesn't have a predecessor + ans.insert(start, None); + + for coord in vec![(0, 1), (1, 0)] { + ans.insert(coord, Some((start, graph[coord.0][coord.1]))); + prio.push(Reverse((graph[coord.0][coord.1], coord, start, 0, 0))); + prio.push(Reverse((graph[coord.0][coord.1], coord, start, 1, 0))); + } + + while let Some(Reverse((dist_new, new, prev, dir, steps))) = prio.pop() { + match ans[&new] { + // what we popped is what is in ans, we'll compute it + Some((p, d)) if p == prev && d == dist_new => {} + // otherwise it's not interesting + _ => continue, + } + + let adj_list: Vec<((usize, usize), u32, i32, i32)> = + get_adjacent_positions(new, dir, steps, ans.get(&new).unwrap().unwrap().0) + .iter() + .filter(|((x, y), _, _)| *x < graph.len() && *y < graph[0].len()) + .map(|(pos, dir, steps)| (*pos, graph[pos.0][pos.1], *dir, *steps)) + .collect(); + + for (next, weight, dir, steps) in adj_list { + match ans.get(&next) { + // if ans[next] is a lower dist than the alternative one, we do nothing + Some(Some((_, dist_next))) if dist_new + weight > *dist_next => {} + // if ans[next] is None then next is start and so the distance won't be changed, it won't be added again in prio + Some(None) => {} + // the new path is shorter, either new was not in ans or it was farther + _ => { + ans.insert(next, Some((new, weight + dist_new))); + prio.push(Reverse((weight + dist_new, next, new, dir, steps))); + } + } + } + } + + ans +} + +fn get_adjacent_positions( + pos: (usize, usize), + dir: i32, + steps: i32, + last_pos: (usize, usize), +) -> Vec<((usize, usize), i32, i32)> { + let mut adj_positions = Vec::new(); + + let directions = vec![(0, 1), (1, 0), (0, -1), (-1, 0)]; // Right, Down, Left, Up + + for &(dx, dy) in &directions { + let new_pos = (pos.0 as isize + dx, pos.1 as isize + dy); + + if new_pos.0 < 0 || new_pos.1 < 0 { + continue; + } + + let new_pos = (new_pos.0 as usize, new_pos.1 as usize); + + if new_pos == last_pos { + // wrong dir + continue; + } + + if new_pos.0.abs_diff(last_pos.0) == 2 || new_pos.1.abs_diff(last_pos.1) == 2 { + if steps < 3 { + adj_positions.push((new_pos, dir, steps + 1)) + } + } else { + let dir = match (dx, dy) { + (0, 1) => 0, + (1, 0) => 1, + (0, -1) => 2, + _ => 3, + }; + adj_positions.push((new_pos, dir, 1)); + } + } + + adj_positions +} + +fn max_in_dir(pos: (usize, usize), prev: &HashMap<(usize, usize), (usize, usize)>) -> bool { + if !prev.contains_key(&pos) + || !prev.contains_key(prev.get(&pos).unwrap()) + || !prev.contains_key(prev.get(prev.get(&pos).unwrap()).unwrap()) + { + return false; + } + + let prev1 = *prev.get(&pos).unwrap(); + let prev2 = *prev.get(&prev1).unwrap(); + let prev3 = *prev.get(&prev2).unwrap(); + + let diff = (prev1.0.abs_diff(pos.0), prev1.1.abs_diff(pos.1)); + + if diff == (prev2.0.abs_diff(prev1.0), prev2.1.abs_diff(prev1.1)) + && diff == (prev3.0.abs_diff(prev2.0), prev3.1.abs_diff(prev2.1)) + { + return true; + } + + false +} + +fn max_in_dir2( + pos: (usize, usize), + prev: &BTreeMap<(usize, usize), Option<((usize, usize), u32)>>, +) -> bool { + if !prev.contains_key(&pos) + || !prev.get(&pos).unwrap().is_some() + || !prev.contains_key(&prev.get(&pos).unwrap().unwrap().0) + || !prev + .get(&prev.get(&pos).unwrap().unwrap().0) + .unwrap() + .is_some() + || !prev.contains_key( + &prev + .get(&prev.get(&pos).unwrap().unwrap().0) + .unwrap() + .unwrap() + .0, + ) + || !prev + .get( + &prev + .get(&prev.get(&pos).unwrap().unwrap().0) + .unwrap() + .unwrap() + .0, + ) + .unwrap() + .is_some() + { + return false; + } + + let prev1 = prev.get(&pos).unwrap().unwrap().0; + let prev2 = prev.get(&prev1).unwrap().unwrap().0; + let prev3 = prev.get(&prev2).unwrap().unwrap().0; + + let diff = (prev1.0.abs_diff(pos.0), prev1.1.abs_diff(pos.1)); + + if diff == (prev2.0.abs_diff(prev1.0), prev2.1.abs_diff(prev1.1)) + && diff == (prev3.0.abs_diff(prev2.0), prev3.1.abs_diff(prev2.1)) + { + return true; + } + + false +} diff --git a/src/main.rs b/src/main.rs index d167d90..00aa7a8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,8 +5,8 @@ extern crate scan_fmt; use std::time::Instant; fn main() { - //days::d16::solve() - _all_days() + days::d17::solve() + //_all_days() } #[allow(unreachable_code, unused)]