diff --git a/data/day12/example01.txt b/data/day12/example01.txt new file mode 100644 index 0000000..433e0d2 --- /dev/null +++ b/data/day12/example01.txt @@ -0,0 +1,5 @@ +Sabqponm +abcryxxl +accszExk +acctuvwj +abdefghi \ No newline at end of file diff --git a/data/day12/input.txt b/data/day12/input.txt new file mode 100644 index 0000000..b226042 --- /dev/null +++ b/data/day12/input.txt @@ -0,0 +1,41 @@ +abcccaaaaaaccccccccaaaaaccccccaaaaaaccccccaaaaaaaacccaaaaaaaccaaaacccccccccccccccccccccccccaaaaaacccccccccccccccccccccccccccccaaaaaa +abcccaaaaaacccccccaaaaaaccccaaaaaaaacccccccaaaaaaaaaaaaaaaaccaaaaacccccccccccccccccccccccccaaaaaacccccccccccccccccccccccccccccaaaaaa +abccccaaaaacaaaccaaaaaaaacccaaaaaaaaacccccccaaaaaaaaaaaaaaaacaaaaaacccccccccaaacccccccccccaaaaaaaaccccccccccaaccccccccccccccccaaaaaa +abccccaaaaccaaaaaaaaaaaaacccaaaaaaaaaacccccaaaaaaaaaaaaaaaaaaacaaaacccccccccaaaacccccccccaaaaaaaaaacccccccccaaaccccccccccccccccccaaa +abcccccccccaaaaaacccaacccccccccaaacaaaccccccaacccccccaaaaaaaaacaacccccccccccaaaacccccccccaaaaaaaaaacccccccccaaaccacaaccccccccccccaaa +abcccccccccaaaaaacccaacccccccccaaacccccccccccccccccccaaaacaaaacccccccaacaaccaaaccccccccccaccaaaaacacccccccccaaaacaaaaccccccccccccaac +abccccccccccaaaaacccccccccccccccacccaaaacccccccccccccaaaacccccccccccccaaaacccccccccccaacccccaaaaccccccccjjjjaaaaaaaaaccccccccccccccc +abccccccccccaaaacccccccccccccccccccaaaaacccccccccccccaaaccccccccccccccaaaaacccccccccaaaaaacccaaccccccccjjjjjjkkaaaacccccccccaacccccc +abcccccaaccccccccccccccccccccccccccaaaaaacccccccccccccaacccccccccccccaaaaaaccccccccccaaaaaccccccccccccjjjjjjjkkkkaacccccaacaaacccccc +abccaaaacccccccccccccccccccccccccccaaaaaaccccccccccccccccccccccccccccaaaacaccccccccaaaaaaaccccaacccccjjjjoooookkkkkkkklllaaaaaaacccc +abccaaaaaacccccccccccccccccccccccccaaaaacccccccccccccccccccccccccccccccaaccccccccccaaaaaaaaccaaaaccccjjjoooooookkkkkkkllllaaaaaacccc +abcccaaaaacccccccccccccccccccccccccccaaaccccccccaaaacccccccccccccccccccccccccccccccaaaaaaaaccaaaaccccjjooooooooppkkppplllllaccaacccc +abccaaaaaccccccccccccaccccccccccccccccccccccccccaaaacccccccccccccccccccccccccccccccccaaacacccaaaacccijjooouuuuoppppppppplllccccccccc +abcccccaacccccccccccaaaaaaaaccccccccccccccccccccaaaaccccaaccccccccaaacccccccccccccaacaaccccccccccccciijoouuuuuuppppppppplllcccaccccc +abcccccccccccccccccccaaaaaaccccccccccccccccccccccaaccccaaaacccccccaaaaccccccccccaaaaaaccccccccccccciiiiootuuuuuupuuuvvpppllccccccccc +abcccccccccccccccccccaaaaaaccaaaaacccccccccccccccccccccaaaacccccccaaaaccccccccccaaaaaaccccccccccccciiinnotuuxxxuuuuvvvpppllccccccccc +abccccccccccccccacccaaaaaaaacaaaaaaacccccccccccccccccccaaaacccccccaaacccccaaaaccaaaaaccccaaccccccciiiinnnttxxxxuuyyyvvqqqllccccccccc +abcccccccccccaaaaccaaaaaaaaaaaaaaaaaaccaacccccccccccccccccccccccccccccccccaaaacccaaaaaccaaacccccciiinnnnnttxxxxxyyyyvvqqqllccccccccc +abaaaacccccccaaaaaaaaaaaaaaaaaaaaaaaaaaaacccccccccccccccccccccccccccccccccaaaacccaaaaaacaaaccccciiinnnnttttxxxxxyyyyvvqqmmmccccccccc +abaaaaccccccccaaaaacccaaaaacaaaaaacaaaaaaccccccccccccccccaaccccccccccccccccaacccccccaaaaaaaaaaciiinnnnttttxxxxxyyyyvvqqqmmmccccccccc +SbaaaacccccccaaaaaccccaaaaaccaaaaaaaaaaaccccccccccccccccaaacaacccccccccccccccccccccccaaaaaaaaachhhnnntttxxxEzzzzyyvvvqqqmmmccccccccc +abaaaacccccccaacaacccccaaaaaaaacaaaaaaaaaccccccccccccccccaaaaaccccccccccccccccccccccccaaaaaaacchhhnnntttxxxxxyyyyyyvvvqqmmmdddcccccc +abaaaacccccccccccccccccccaaaaaacaaaaaaaaaacccccccccccccaaaaaaccccccccaaaccccccccccccccaaaaaaccchhhnnntttxxxxywyyyyyyvvvqqmmmdddccccc +abaacccccccccccccccccccaaaaaaacccccaaaaaaacccccccccccccaaaaaaaacccccaaaacccccccccccccaaaaaaacaahhhmmmttttxxwwyyyyyyyvvvqqmmmdddccccc +abcccccccccccccccccccccaaaaaaacaaccaaacccccccccccccccccaacaaaaacccccaaaacccccccccccccaaacaaaaaahhhmmmmtsssswwyywwwwvvvvqqqmmdddccccc +abcccccccccccccccaaaccccaaaaaaaaaacaaccaaccccccccccccccccaaacaccccccaaaacccccccccccccccccaaaaacahhhmmmmmsssswwywwwwwvvrrqqmmdddccccc +abcccccccccccccaaaaaaccccaaaaaaaaaccaaaacccccccccccccccccaacccccccccccccccccccccccaaaccccaaaaaaahhhhhmmmmssswwwwwrrrrrrrrmmmmddccccc +abcccccccccccccaaaaaaccccaaaaaaaaaaaaaaaaaccccccccccccccccccccccccccccccccccccccaaaaaacccccaaaaachhhhhmmmmsswwwwrrrrrrrrrkkmdddccccc +abccccccccccccccaaaaaccccccaaaaaaaaaaaaaaaccccccccccccccccccccccccccccccccccccccaaaaaaccccaaaaacccchhggmmmssswwrrrrrkkkkkkkkdddacccc +abccaaaacccccccaaaaacccccccccaaaaaacaaaaacccccccccccccccccccccccccccccccccccccccaaaaaaccccaacaaaccccggggmmsssssrrlkkkkkkkkkdddaccccc +abccaaaacccccccaaaaacccccccccaaaaaaccccaacccccccccccccccccccccccccccccccccccccccaaaaaccccccccaaccccccgggmllssssrllkkkkkkkeeeddaccccc +abccaaaacccccccaaacccccccccccaaaaaacccccccccccccccccccaacccccccccccccccccccccccaaaaaacccccccccccccccccggllllssslllkkeeeeeeeeeaaacccc +abcccaaccccccccaaacaaaccccccaaaaaaaaaaacccccccccccccaaaaaacccccccccccccccccccccaaacaaacccccaacccccccccggglllllllllfeeeeeeeeaaaaacccc +abccccccccccaaaaaaaaaaccccccccccccaccaaaccacccccccccaaaaaaccccaaccaacccaaccccccaaaaaaacccccaaccccccccccggglllllllfffeeecccaaaaaacccc +abccccccccccaaaaaaaaacccccccccccccccaaaaaaaccccccccccaaaaaccccaaaaaacccaaaaaaccaaaaaacccaaaaaaaacccccccggggllllfffffccccccaacccccccc +abcccccccccccaaaaaaacccccccccccccccccaaaaaaccaacccccaaaaaccccccaaaaacccaaaaaacaaaaaaacccaaaaaaaaccccccccgggffffffffccccccccccccccccc +abccccccccccccaaaaaaacccccccccccccaaaaaaaaacaaaaccccaaaaacaaaaaaaaaacaaaaaaacaaaaaaaaaccccaaaacccccccccccggffffffacccccccccccccccaaa +abccccccccccccaaaaaaacaaccccccccccaaaaaaaaacaaaacccccaaaaaaaaaaaaaaaaaaaaaaacaaaaaaaaaacccaaaaacccccccccccaffffaaaaccccccccccccccaaa +abccccccccccccaaacaaaaaacccccccccccaaaaaaaacaaaaaaaaaaaaaaaaaaaaaaaaacaaaaaaacccaaacaaaccaaaaaacccccccccccccccccaaaccccccccccccccaaa +abccccccccccccaaccaaaaaccccccccccccccaaaaaaaccccaaaaaaaaaaaaccccaacccccaaaaaacccaaaccccccaaccaacccccccccccccccccaaacccccccccccaaaaaa +abcccccccccccccccaaaaaaaaccccccccccccaacccacccccccaaaaaaaaaaccccaacccccaaccccccccaccccccccccccccccccccccccccccccccccccccccccccaaaaaa \ No newline at end of file diff --git a/src/days/day12/mod.rs b/src/days/day12/mod.rs new file mode 100644 index 0000000..c3f6ab1 --- /dev/null +++ b/src/days/day12/mod.rs @@ -0,0 +1,294 @@ +use std::collections::{BinaryHeap, HashSet}; + +use crate::common::pos::Pos; + +use super::template::{DayTrait, ResultType}; +use thiserror::Error; + +const DAY_NUMBER: usize = 12; + +pub struct Day; + +impl DayTrait for Day { + fn get_day_number(&self) -> usize { + DAY_NUMBER + } + + fn part1(&self, lines: &[String]) -> anyhow::Result { + let valley = Valley::parse(lines)?; + Ok(ResultType::Integer(valley.walk()? as i64)) + } + + fn part2(&self, lines: &[String]) -> anyhow::Result { + let valley = Valley::parse(lines)?; + Ok(ResultType::Integer(valley.walk_short()? as i64)) + } +} + +#[derive(Debug, Error)] +enum ValleyError { + #[error("Not a legal terrain char: {0}")] + NotALegalCharacter(char), + #[error("Valley needs to be rectangle")] + NotAReactangleValley, + #[error("Valley map conatins no data")] + EmptyValley, + #[error("Could not find start point")] + NoStartFound, + #[error("Could not find exit point")] + NoExitFound, + #[error("No path found")] + NoPathFound, +} + +#[derive(Debug, PartialEq, Eq)] +struct Path { + length: usize, + height: char, + pos: Pos, +} + +impl PartialOrd for Path { + fn partial_cmp(&self, other: &Self) -> Option { + match other.length.partial_cmp(&self.length) { + Some(core::cmp::Ordering::Equal) => {} + ord => return ord, + } + match other.height.partial_cmp(&self.height) { + Some(core::cmp::Ordering::Equal) => {} + ord => return ord, + } + match self.pos.x().partial_cmp(&other.pos.x()) { + Some(core::cmp::Ordering::Equal) => {} + ord => return ord, + } + self.pos.y().partial_cmp(&other.pos.y()) + } +} + +impl Ord for Path { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + other + .length + .cmp(&self.length) + .then_with(|| other.height.cmp(&self.height)) + .then_with(|| self.pos.x().cmp(&other.pos.x())) + .then_with(|| self.pos.y().cmp(&other.pos.y())) + } +} + +impl Path { + pub fn new(length: usize, height: char, pos: Pos) -> Self { + Path { + length, + height, + pos, + } + } + + pub fn next_path<'a>(&'a self, valley: &'a Valley) -> Neighbors<'a> { + Neighbors::new(self, valley) + } +} + +struct Neighbors<'a> { + path: &'a Path, + valley: &'a Valley, + state: usize, +} + +impl<'a> Neighbors<'a> { + pub fn new(path: &'a Path, valley: &'a Valley) -> Self { + Neighbors { + path, + state: 0, + valley, + } + } + + fn next_pos(&mut self) -> Option> { + while self.state < 4 { + self.state += 1; + match self.state { + 1 => { + if self.path.pos.x() < self.valley.width() - 1 { + return Some(Pos::new(self.path.pos.x() + 1, self.path.pos.y())); + } + } + 2 => { + if self.path.pos.y() > 0 { + return Some(Pos::new(self.path.pos.x(), self.path.pos.y() - 1)); + } + } + 3 => { + if self.path.pos.x() > 0 { + return Some(Pos::new(self.path.pos.x() - 1, self.path.pos.y())); + } + } + 4 => { + if self.path.pos.y() < self.valley.height() - 1 { + return Some(Pos::new(self.path.pos.x(), self.path.pos.y() + 1)); + } + } + _ => {} + } + } + None + } +} + +impl Iterator for Neighbors<'_> { + type Item = Path; + + fn next(&mut self) -> Option { + while let Some(pos) = self.next_pos() { + let height = self.valley.get_height(pos); + if height as u32 + 1 >= self.path.height as u32 { + return Some(Path::new(self.path.length + 1, height, pos)); + } + } + None + } +} + +struct Valley { + map: Vec>, + start: Pos, + exit: Pos, + width: usize, +} + +impl Valley { + pub fn parse(lines: &[String]) -> Result { + let mut map = Vec::new(); + let mut start = None; + let mut exit = None; + let mut valley_width = None; + for (y, row) in lines.iter().enumerate() { + let mut height_row = Vec::new(); + for (x, height_char) in row.chars().enumerate() { + match height_char { + 'S' => { + start = Some(Pos::new(x, y)); + height_row.push('a') + } + 'E' => { + exit = Some(Pos::new(x, y)); + height_row.push('z') + } + 'a'..='z' => height_row.push(height_char), + _ => return Err(ValleyError::NotALegalCharacter(height_char)), + } + } + if let Some(width) = valley_width { + if width != height_row.len() { + return Err(ValleyError::NotAReactangleValley); + } + } else { + valley_width = Some(height_row.len()); + } + map.push(height_row); + } + let Some(width) = valley_width else { + return Err(ValleyError::EmptyValley); + }; + let Some(start) = start else { + return Err(ValleyError::NoStartFound); + }; + let Some(exit) = exit else { + return Err(ValleyError::NoExitFound); + }; + Ok(Valley { + map, + start, + exit, + width, + }) + } + + fn get_height(&self, pos: Pos) -> char { + self.map[pos.y()][pos.x()] + } + + fn do_walk(&self, check: F) -> Result + where + F: Fn(Pos) -> bool, + { + let mut shortest = HashSet::with_capacity(self.width * self.map.len()); + let mut queue = BinaryHeap::new(); + queue.push(Path::new(0, 'z', self.exit)); + while let Some(current) = queue.pop() { + if check(current.pos) { + return Ok(current.length); + } + if shortest.contains(¤t.pos) { + continue; + } + shortest.insert(current.pos); + for next in current.next_path(self) { + queue.push(next); + } + } + + Err(ValleyError::NoPathFound) + } + + pub fn walk(&self) -> Result { + self.do_walk(|pos| pos == self.start) + } + + pub fn walk_short(&self) -> Result { + self.do_walk(|pos| self.get_height(pos) == 'a') + } + + #[inline] + fn width(&self) -> usize { + self.width + } + + #[inline] + fn height(&self) -> usize { + self.map.len() + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::common::file::read_lines; + use anyhow::Result; + + #[test] + fn test_parse() -> Result<()> { + let day = Day {}; + let lines = read_lines(day.get_day_number(), "example01.txt")?; + let valley = Valley::parse(&lines)?; + assert_eq!(valley.width, 8); + assert_eq!(valley.start, Pos::new(0, 0)); + assert_eq!(valley.exit, Pos::new(5, 2)); + + Ok(()) + } + + #[test] + fn test_part1() -> Result<()> { + let day = Day {}; + let lines = read_lines(day.get_day_number(), "example01.txt")?; + let expected = ResultType::Integer(31); + let result = day.part1(&lines)?; + assert_eq!(result, expected); + + Ok(()) + } + + #[test] + fn test_part2() -> Result<()> { + let day = Day {}; + let lines = read_lines(day.get_day_number(), "example01.txt")?; + let expected = ResultType::Integer(29); + let result = day.part2(&lines)?; + assert_eq!(result, expected); + + Ok(()) + } +} diff --git a/src/days/mod.rs b/src/days/mod.rs index 7cd8179..0602f75 100644 --- a/src/days/mod.rs +++ b/src/days/mod.rs @@ -9,6 +9,7 @@ mod day08; mod day09; mod day10; mod day11; +mod day12; mod template; pub use template::DayTrait; @@ -18,7 +19,7 @@ pub mod day_provider { use super::*; use thiserror::Error; - const MAX_DAY: usize = 11; + const MAX_DAY: usize = 12; pub fn get_day(day_num: usize) -> Result, ProviderError> { match day_num { @@ -33,6 +34,7 @@ pub mod day_provider { 9 => Ok(Box::new(day09::Day)), 10 => Ok(Box::new(day10::Day)), 11 => Ok(Box::new(day11::Day)), + 12 => Ok(Box::new(day12::Day)), _ => Err(ProviderError::InvalidNumber(day_num)), } }