From 822157e3bb1ab1935f08b9b57a5c805644e6abdd Mon Sep 17 00:00:00 2001 From: "Timothy J. Warren" Date: Thu, 7 Dec 2023 15:45:58 -0500 Subject: [PATCH] Complete 2023 Day 3 --- 2023/day2/README.md | 2 +- 2023/day3/README.md | 41 ++++++++++++++++ 2023/day3/src/main.rs | 112 ++++++++++++++++++++++++++++++++++++++---- 3 files changed, 145 insertions(+), 10 deletions(-) diff --git a/2023/day2/README.md b/2023/day2/README.md index 727316b..807bf8f 100644 --- a/2023/day2/README.md +++ b/2023/day2/README.md @@ -45,7 +45,7 @@ IDs of the games that would have been possible, you get **8**. Determine which games would have been possible if the bag had been loaded with only 12 red cubes, 13 green cubes, and 14 blue cubes. **What is the sum of the IDs of those games?** -## Part Two +## Part 2 The Elf says they've stopped producing snow because they aren't getting any **water**! He isn't sure why the water stopped; however, he can show you how to get to the water source to check it out for yourself. It's just up ahead! diff --git a/2023/day3/README.md b/2023/day3/README.md index 655000c..8d166a2 100644 --- a/2023/day3/README.md +++ b/2023/day3/README.md @@ -39,3 +39,44 @@ In this schematic, two numbers are **not** part numbers because they are not adj Of course, the actual engine schematic is much larger. **What is the sum of all of the part numbers in the engine schematic?** + +## Part 2 + +The engineer finds the missing part and installs it in the engine! As the engine springs to life, you jump in the +closest gondola, finally ready to ascend to the water source. + +You don't seem to be going very fast, though. Maybe something is still wrong? Fortunately, the gondola has a phone +labeled "help", so you pick it up and the engineer answers. + +Before you can explain the situation, she suggests that you look out the window. There stands the engineer, holding a +phone in one hand and waving with the other. You're going so slowly that you haven't even left the station. You exit +the gondola. + +The missing part wasn't the only issue - one of the gears in the engine is wrong. A **gear** is any `*` symbol that is +adjacent to **exactly two part numbers**. Its **gear ratio** is the result of multiplying those two numbers together. + +This time, you need to find the gear ratio of every gear and add them all up so that the engineer can figure out +which gear needs to be replaced. + +Consider the same engine schematic again: + +``` +467..114.. FAILED + +...*...... +..35..633. +......#... +617*...... +.....+.58. +..592..... +......755. +...$.*.... +.664.598.. +``` + +In this schematic, there are **two** gears. The first is in the top left; it has part numbers `467` and `35`, so its +gear ratio is `16345`. The second gear is in the lower right; its gear ratio is `451490`. (The * adjacent to `617` is +**not** a gear because it is only adjacent to one part number.) Adding up all of the gear ratios produces `**467835**`. + +**What is the sum of all of the gear ratios in your engine schematic?** + diff --git a/2023/day3/src/main.rs b/2023/day3/src/main.rs index c544a84..42271d0 100644 --- a/2023/day3/src/main.rs +++ b/2023/day3/src/main.rs @@ -105,7 +105,7 @@ impl Grid { (idx % self.num_cols(), idx / self.num_cols()) } - fn adjacent_ind(&self, r: Range) -> HashSet { + fn adjacent_ind_range(&self, r: Range) -> HashSet { let min = r.start; let max = r.end - 1; let mut ind = HashSet::new(); @@ -154,16 +154,20 @@ impl Grid { ind } - fn find_adjacent(&self, r: Range) -> Vec { - self.adjacent_ind(r) + fn find_adjacent_range(&self, r: Range) -> Vec { + self.adjacent_ind_range(r) .into_iter() .map(|ind| self.values[ind]) .collect() } + fn find_adjacent(&self, i: usize) -> HashSet { + HashSet::from_iter(self.find_adjacent_range(i..i + 1).iter().cloned()) + } + fn is_part_number(&self, r: Range) -> bool { use ValueType::*; - self.find_adjacent(r).into_iter().any(|t| match t { + self.find_adjacent_range(r).into_iter().any(|t| match t { Symbol(_) => true, _ => false, }) @@ -180,13 +184,85 @@ impl Grid { .collect() } - fn get_unique_part_numbers(&self) -> HashSet { - HashSet::from_iter(self.get_part_numbers().iter().cloned()) + fn get_part_number_ind(&self) -> Vec { + self.numbers + .iter() + .enumerate() + .filter(|(_, nl)| { + let range = nl.start..nl.end; + self.is_part_number(range) + }) + .map(|(id, _)| id) + .collect() + } + + fn get_gears(&self) -> Vec { + self.values + .clone() + .into_iter() + .enumerate() + .filter(|(_, vt)| match vt { + ValueType::Symbol('*') => true, + _ => false, + }) + .map(|(id, _)| id) + .collect() + } + + fn get_gear_ratios(&self) -> Vec<(usize, usize)> { + let valid_part_number_ind = self.get_part_number_ind(); + let potential_gear_symbols = self.get_gears(); + + // Valid gear ratios are two valid part numbers adjacent to the gear symbol + let raw_gear_ratios: Vec<_> = potential_gear_symbols + .into_iter() + .filter_map(|s| { + let mut number_count = 0; + let gr: Vec<_> = self + .find_adjacent(s) + .into_iter() + .filter(|vt| match vt { + ValueType::Number(id) => valid_part_number_ind.contains(id), + _ => false, + }) + .collect(); + + gr.iter().for_each(|vt| match vt { + ValueType::Number(_) => number_count += 1, + _ => {} + }); + + match number_count { + 2 => Some(gr), + _ => None, + } + }) + .collect(); + + raw_gear_ratios + .into_iter() + .map(|v| { + v.into_iter() + .map(|vt| match vt { + ValueType::Number(id) => self.numbers[id].to_number(), + _ => panic!("Invalid gear ratio!"), + }) + .collect::>() + }) + .map(|gr| (gr[0], gr[1])) + .collect() } fn get_part_number_sum(&self) -> usize { self.get_part_numbers().iter().sum() } + + fn get_gear_ratio_sum(&self) -> usize { + self.get_gear_ratios() + .into_iter() + .map(|(gr0, gr1)| gr0 * gr1) + .sum() + } } fn part_one() { @@ -197,8 +273,14 @@ fn part_one() { ); } +fn part_two() { + let grid = Grid::parse(FILE_STR); + println!("Part 2: Sum of gear ratios: {}", grid.get_gear_ratio_sum()) +} + fn main() { part_one(); + part_two(); } #[cfg(test)] @@ -206,7 +288,6 @@ mod tests { const EXAMPLE_FILE_STR: &'static str = include_str!("example_input.txt"); use super::*; use FromIterator; - use ValueType::*; #[test] fn test_get_part_number_sum_part_1() { @@ -215,9 +296,15 @@ mod tests { } #[test] - fn test_adjacent_index() { + fn test_get_gear_ratio_sum_part_2() { + let grid = Grid::parse(FILE_STR); + assert_eq!(grid.get_gear_ratio_sum(), 84289137); + } + + #[test] + fn test_adjacent_ind_range() { let grid = Grid::parse(EXAMPLE_FILE_STR); - let actual = grid.adjacent_ind(62..65); + let actual = grid.adjacent_ind_range(62..65); assert_eq!(12, actual.len()); assert_eq!( HashSet::from_iter(actual.iter().cloned()), @@ -237,4 +324,11 @@ mod tests { let grid = Grid::parse(EXAMPLE_FILE_STR); assert_eq!(grid.get_part_number_sum(), 4361); } + + #[test] + fn test_get_gear_ratio_sum() { + let grid = Grid::parse(EXAMPLE_FILE_STR); + let sum = grid.get_gear_ratio_sum(); + assert_eq!(sum, 467835); + } }