diff --git a/samples/day12.txt b/samples/day12.txt new file mode 100644 index 0000000..433e0d2 --- /dev/null +++ b/samples/day12.txt @@ -0,0 +1,5 @@ +Sabqponm +abcryxxl +accszExk +acctuvwj +abdefghi \ No newline at end of file diff --git a/src/bin/day12.rs b/src/bin/day12.rs new file mode 100644 index 0000000..fee937b --- /dev/null +++ b/src/bin/day12.rs @@ -0,0 +1,146 @@ +use std::{ + collections::{BTreeSet, VecDeque}, +}; + +use aoc_2022::*; + +use itertools::Itertools; + +type Input = Vec>; +type Output = usize; + +type Position = Vector2D; + +fn find(map: &[Vec], expected: char) -> Option { + for (y, row) in map.iter().enumerate() { + for (x, &c) in row.iter().enumerate() { + if c == expected { + return Some(Position::new(x as isize, y as isize)); + } + } + } + + None +} + +fn find_start(map: &[Vec]) -> Position { + if let Some(pos) = find(map, 'S') { + pos + } else { + panic!("haven't found start") + } +} + +fn find_target(map: &[Vec]) -> Position { + if let Some(pos) = find(map, 'E') { + pos + } else { + panic!("haven't found target") + } +} + +#[derive(Debug)] +struct Vertex { + position: Position, + distance: usize, +} + +impl Vertex { + fn new(position: Position, distance: usize) -> Self { + Self { position, distance } + } +} + +fn get_elevation(c: char) -> i32 { + match c { + 'S' => get_elevation('a'), + 'E' => get_elevation('z'), + _ => c as i32 - 'a' as i32, + } +} + +struct Day12; +impl Solution for Day12 { + fn parse_input>(pathname: P) -> Input { + file_to_lines(pathname) + .into_iter() + .map(|line| line.chars().collect_vec()) + .collect_vec() + } + + fn part_1(input: &Input) -> Output { + let start = find_start(input); + let target = find_target(input); + + let mut visited: BTreeSet = BTreeSet::new(); + let mut queue: VecDeque = VecDeque::new(); + visited.insert(start); + queue.push_back(Vertex::new(start, 0)); + + while let Some(v) = queue.pop_front() { + // debug!("Taking {:?} from queue", v); + + for (dx, dy) in [(0, 1), (1, 0), (0, -1), (-1, 0)] { + let d = Position::new(dx, dy); + let neighbour = v.position + d; + + let current = get_elevation(*index(input, &v.position)); + if in_range(input, &neighbour) + && (get_elevation(*index(input, &neighbour)) <= current + 1) + && !visited.contains(&neighbour) + { + if neighbour == target { + // debug!("Found target at distance {}", v.distance + 1); + return v.distance + 1; + } + + visited.insert(neighbour); + queue.push_back(Vertex::new(neighbour, v.distance + 1)); + } + } + } + + panic!("haven't found path to target") + } + + fn part_2(input: &Input) -> Output { + let start = find_target(input); + + let mut visited: BTreeSet = BTreeSet::new(); + let mut queue: VecDeque = VecDeque::new(); + visited.insert(start); + queue.push_back(Vertex::new(start, 0)); + + while let Some(v) = queue.pop_front() { + // debug!("Taking {:?} from queue at elevation {}", v, get_elevation(*index(input, &v.position))); + + for (dx, dy) in [(0, 1), (1, 0), (0, -1), (-1, 0)] { + let d = Position::new(dx, dy); + let neighbour = v.position + d; + + let current = get_elevation(*index(input, &v.position)); + if in_range(input, &neighbour) + && (current - get_elevation(*index(input, &neighbour))) <= 1 + && !visited.contains(&neighbour) + { + if get_elevation(*index(input, &neighbour)) == 0 { + // debug!("Found target at distance {}", v.distance + 1); + return v.distance + 1; + } + + visited.insert(neighbour); + queue.push_back(Vertex::new(neighbour, v.distance + 1)); + } + } + } + + panic!("haven't found path to target") + } +} + +fn main() -> Result<()> { + // Day12::run("sample") + Day12::main() +} + +test_sample!(day_12, Day12, 31, 29);