From 220483af05f8d0ba43480b0317fed2454f402769 Mon Sep 17 00:00:00 2001 From: Burnus Date: Tue, 10 Dec 2024 16:46:53 +0100 Subject: [PATCH] Add 2024 Day 10 --- 2024/day10_hoof_it/Cargo.toml | 13 +++ 2024/day10_hoof_it/challenge.md | 84 +++++++++++++++ 2024/day10_hoof_it/src/lib.rs | 132 +++++++++++++++++++++++ 2024/day10_hoof_it/tests/challenge_input | 41 +++++++ 2024/day10_hoof_it/tests/sample_input | 8 ++ 5 files changed, 278 insertions(+) create mode 100644 2024/day10_hoof_it/Cargo.toml create mode 100644 2024/day10_hoof_it/challenge.md create mode 100644 2024/day10_hoof_it/src/lib.rs create mode 100644 2024/day10_hoof_it/tests/challenge_input create mode 100644 2024/day10_hoof_it/tests/sample_input diff --git a/2024/day10_hoof_it/Cargo.toml b/2024/day10_hoof_it/Cargo.toml new file mode 100644 index 0000000..153ee98 --- /dev/null +++ b/2024/day10_hoof_it/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "day10_hoof_it" +version = "0.1.0" +edition = "2021" + +[dependencies] + +[dev-dependencies] +# criterion = "0.5.1" + +[[bench]] +name = "test_benchmark" +harness = false diff --git a/2024/day10_hoof_it/challenge.md b/2024/day10_hoof_it/challenge.md new file mode 100644 index 0000000..dc3fa95 --- /dev/null +++ b/2024/day10_hoof_it/challenge.md @@ -0,0 +1,84 @@ +You all arrive at a [Lava Production Facility](/2023/day/15) on a floating island in the sky. As the others begin to search the massive industrial complex, you feel a small nose boop your leg and look down to discover a reindeer wearing a hard hat. + +The reindeer is holding a book titled "Lava Island Hiking Guide". However, when you open the book, you discover that most of it seems to have been scorched by lava! As you're about to ask how you can help, the reindeer brings you a blank [topographic map](https://en.wikipedia.org/wiki/Topographic_map) of the surrounding area (your puzzle input) and looks up at you excitedly. + +Perhaps you can help fill in the missing hiking trails? + +The topographic map indicates the *height* at each position using a scale from `0` (lowest) to `9` (highest). For example: + +``` +0123 +1234 +8765 +9876 + +``` + +Based on un-scorched scraps of the book, you determine that a good hiking trail is *as long as possible* and has an *even, gradual, uphill slope*. For all practical purposes, this means that a *hiking trail* is any path that starts at height `0`, ends at height `9`, and always increases by a height of exactly 1 at each step. Hiking trails never include diagonal steps - only up, down, left, or right (from the perspective of the map). + +You look up from the map and notice that the reindeer has helpfully begun to construct a small pile of pencils, markers, rulers, compasses, stickers, and other equipment you might need to update the map with hiking trails. + +A *trailhead* is any position that starts one or more hiking trails - here, these positions will always have height `0`. Assembling more fragments of pages, you establish that a trailhead's *score* is the number of `9`-height positions reachable from that trailhead via a hiking trail. In the above example, the single trailhead in the top left corner has a score of `1` because it can reach a single `9` (the one in the bottom left). + +This trailhead has a score of `2`: + +``` +...0... +...1... +...2... +6543456 +7.....7 +8.....8 +9.....9 + +``` + +(The positions marked `.` are impassable tiles to simplify these examples; they do not appear on your actual topographic map.) + +This trailhead has a score of `4` because every `9` is reachable via a hiking trail except the one immediately to the left of the trailhead: + +``` +..90..9 +...1.98 +...2..7 +6543456 +765.987 +876.... +987.... + +``` + +This topographic map contains *two* trailheads; the trailhead at the top has a score of `1`, while the trailhead at the bottom has a score of `2`: + +``` +10..9.. +2...8.. +3...7.. +4567654 +...8..3 +...9..2 +.....01 + +``` + +Here's a larger example: + +``` +89010123 +78121874 +87430965 +96549874 +45678903 +32019012 +01329801 +10456732 + +``` + +This larger example has 9 trailheads. Considering the trailheads in reading order, they have scores of `5`, `6`, `5`, `3`, `1`, `3`, `5`, `3`, and `5`. Adding these scores together, the sum of the scores of all trailheads is `*36*`. + +The reindeer gleefully carries over a protractor and adds it to the pile. *What is the sum of the scores of all trailheads on your topographic map?* + +To begin, [get your puzzle input](10/input). + +Answer: \ No newline at end of file diff --git a/2024/day10_hoof_it/src/lib.rs b/2024/day10_hoof_it/src/lib.rs new file mode 100644 index 0000000..00d28f2 --- /dev/null +++ b/2024/day10_hoof_it/src/lib.rs @@ -0,0 +1,132 @@ +use core::fmt::Display; +use std::collections::HashSet; + +#[derive(Debug, PartialEq, Eq)] +pub enum ParseError { + EmptyMap, + InvalidHeight(char), + NonRectangular, +} + +impl Display for ParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::EmptyMap => write!(f, "Input must not be empty."), + Self::InvalidHeight(e) => write!(f, "Unable to parse {e} into a height"), + Self::NonRectangular => write!(f, "All input lines must have equal length"), + } + } +} + +type Coordinates = (isize, isize); + +struct Map { + tiles: Vec>, + trailheads: Vec<(Coordinates, usize, usize)>, + width: isize, + height: isize, +} + +impl TryFrom<&str> for Map { + type Error = ParseError; + + fn try_from(value: &str) -> Result { + let width = value.lines().next().ok_or(Self::Error::EmptyMap).map(|line| line.len())?; + let mut trailheads = Vec::new(); + let tiles = value.lines().enumerate().map(|(y, line)| { + if line.len() != width { + return Err(Self::Error::NonRectangular); + } + line.chars().enumerate().map(|(x, c)| { + match c { + '0' => { + trailheads.push(((x as isize, y as isize), 0, 0)); + Ok(0) + }, + d if d.is_ascii_digit() => Ok(d as u8 - b'0'), + e => Err(Self::Error::InvalidHeight(e)), + } + + }).collect::>() + }).collect::, _>>()?; + + let mut res = Self { + height: tiles.len() as isize, + tiles, + trailheads, + width: width as isize, + }; + (0..res.trailheads.len()).for_each(|idx| res.rate_trailhead(idx)); + + Ok(res) + } +} + +impl Map { + /// Returns the elevation at valid map `Coordinates`, or `u8::MAX`, if they are out of bounds. + fn elevation(&self, at: Coordinates) -> u8 { + if at.0 >= 0 && at.1 >= 0 && at.0 < self.width && at.1 < self.height { + self.tiles[at.1 as usize][at.0 as usize] + } else { + u8::MAX + } + } + + /// Assigns the score and rating to the trailhead at the given index. + fn rate_trailhead(&mut self, trailhead_idx: usize) { + let (score, rating) = if let Some (trailhead) = self.trailheads.get(trailhead_idx) { + let start = trailhead.0; + let mut open_set = Vec::from([(start, 0)]); + let mut destinations = HashSet::new(); + let mut trails = 0; + while let Some((pos, elevation)) = open_set.pop() { + if elevation == 9 { + trails += 1; + destinations.insert(pos); + continue; + } + [(-1,0), (1,0), (0,-1), (0,1)] + .iter() + .filter(|(dx, dy)| self.elevation((pos.0+dx, pos.1+dy)) == elevation + 1) + .for_each(|(dx, dy)| { + let next = (pos.0+dx, pos.1+dy); + open_set.push((next, elevation+1)); + }); + } + (destinations.len(), trails) + } else { + return + }; + self.trailheads[trailhead_idx].1 = score; + self.trailheads[trailhead_idx].2 = rating; + } +} + +pub fn run(input: &str) -> Result<(usize, usize), ParseError> { + let map = Map::try_from(input)?; + let first = map.trailheads.iter().map(|&t| t.1).sum(); + let second = map.trailheads.iter().map(|&t| t.2).sum(); + Ok((first, second)) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::fs::read_to_string; + + fn read_file(name: &str) -> String { + read_to_string(name).expect(&format!("Unable to read file: {name}")[..]).trim().to_string() + } + + #[test] + fn test_sample() { + let sample_input = read_file("tests/sample_input"); + assert_eq!(run(&sample_input), Ok((36, 81))); + } + + #[test] + fn test_challenge() { + let challenge_input = read_file("tests/challenge_input"); + assert_eq!(run(&challenge_input), Ok((535, 1186))); + } +} diff --git a/2024/day10_hoof_it/tests/challenge_input b/2024/day10_hoof_it/tests/challenge_input new file mode 100644 index 0000000..c0caf6e --- /dev/null +++ b/2024/day10_hoof_it/tests/challenge_input @@ -0,0 +1,41 @@ +21056789894321015012980123498787601045687 +32346543765601156703474544567896012138796 +10107632659892349821563678989345643029655 +23218101256781015430412101071234754910544 +14569870345432016321003012560109867801033 +05678963256789127879854303456078105932122 +18723450109898038910765210010563254541001 +69011201298788945125898998323454569679852 +78760345345654876034787677401235378089760 +89456896012503876965014586512765432123601 +32387987003412965872123298703890101984512 +01291876121101234989456107654965278854603 +00180145030980325678327890217874369763254 +14343232145671013454310691307893454610169 +23432165430110302169234582456732108905678 +96547078923231231078105673454321097214523 +87678432214348940987017894765870986341014 +96589541009857654320123765891963475498765 +01434630111766789010432143210452560167876 +32345721210951999016549054789301001456965 +21076890129810878321678945643219812332109 +78789001234723765430321238756106734323458 +09654160345654210389210109987005125010767 +12323271657601345678780125678014056541893 +25414987798542343456690034329123987932012 +56905677810439652987501145016510345801101 +67856298923128721987432156987421256789210 +22347107654098910876543087870330107654301 +11298231012347654305678898981278798987652 +00109545691056763211219767874569678690343 +12065456783345894320105656913450569541289 +43874321092132101899234541002321256632176 +54930010561001234788765432211056746789045 +67821123472390545654123101347849839874330 +78934010589487698763034567456931028765221 +67785697696590101672105698741022210540100 +58696788787781234589789789012013307632101 +49545459810170301075674654323454458989032 +30432365923965432165463210498569567976543 +21021876854876523678354112347678678875301 +30010966763987014589210001456565589765432 diff --git a/2024/day10_hoof_it/tests/sample_input b/2024/day10_hoof_it/tests/sample_input new file mode 100644 index 0000000..cada9b3 --- /dev/null +++ b/2024/day10_hoof_it/tests/sample_input @@ -0,0 +1,8 @@ +89010123 +78121874 +87430965 +96549874 +45678903 +32019012 +01329801 +10456732