Complete 2023 Day 3

This commit is contained in:
Timothy Warren 2023-12-07 15:45:58 -05:00
parent 979d241bb8
commit 822157e3bb
3 changed files with 145 additions and 10 deletions

View File

@ -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 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?** 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; 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! however, he can show you how to get to the water source to check it out for yourself. It's just up ahead!

View File

@ -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. Of course, the actual engine schematic is much larger.
**What is the sum of all of the part numbers in the engine schematic?** **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?**

View File

@ -105,7 +105,7 @@ impl Grid {
(idx % self.num_cols(), idx / self.num_cols()) (idx % self.num_cols(), idx / self.num_cols())
} }
fn adjacent_ind(&self, r: Range<usize>) -> HashSet<usize> { fn adjacent_ind_range(&self, r: Range<usize>) -> HashSet<usize> {
let min = r.start; let min = r.start;
let max = r.end - 1; let max = r.end - 1;
let mut ind = HashSet::new(); let mut ind = HashSet::new();
@ -154,16 +154,20 @@ impl Grid {
ind ind
} }
fn find_adjacent(&self, r: Range<usize>) -> Vec<ValueType> { fn find_adjacent_range(&self, r: Range<usize>) -> Vec<ValueType> {
self.adjacent_ind(r) self.adjacent_ind_range(r)
.into_iter() .into_iter()
.map(|ind| self.values[ind]) .map(|ind| self.values[ind])
.collect() .collect()
} }
fn find_adjacent(&self, i: usize) -> HashSet<ValueType> {
HashSet::from_iter(self.find_adjacent_range(i..i + 1).iter().cloned())
}
fn is_part_number(&self, r: Range<usize>) -> bool { fn is_part_number(&self, r: Range<usize>) -> bool {
use ValueType::*; 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, Symbol(_) => true,
_ => false, _ => false,
}) })
@ -180,13 +184,85 @@ impl Grid {
.collect() .collect()
} }
fn get_unique_part_numbers(&self) -> HashSet<usize> { fn get_part_number_ind(&self) -> Vec<usize> {
HashSet::from_iter(self.get_part_numbers().iter().cloned()) 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<usize> {
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::<Vec<usize>>()
})
.map(|gr| (gr[0], gr[1]))
.collect()
} }
fn get_part_number_sum(&self) -> usize { fn get_part_number_sum(&self) -> usize {
self.get_part_numbers().iter().sum() 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() { 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() { fn main() {
part_one(); part_one();
part_two();
} }
#[cfg(test)] #[cfg(test)]
@ -206,7 +288,6 @@ mod tests {
const EXAMPLE_FILE_STR: &'static str = include_str!("example_input.txt"); const EXAMPLE_FILE_STR: &'static str = include_str!("example_input.txt");
use super::*; use super::*;
use FromIterator; use FromIterator;
use ValueType::*;
#[test] #[test]
fn test_get_part_number_sum_part_1() { fn test_get_part_number_sum_part_1() {
@ -215,9 +296,15 @@ mod tests {
} }
#[test] #[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 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!(12, actual.len());
assert_eq!( assert_eq!(
HashSet::from_iter(actual.iter().cloned()), HashSet::from_iter(actual.iter().cloned()),
@ -237,4 +324,11 @@ mod tests {
let grid = Grid::parse(EXAMPLE_FILE_STR); let grid = Grid::parse(EXAMPLE_FILE_STR);
assert_eq!(grid.get_part_number_sum(), 4361); 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);
}
} }