diff --git a/2023/day3/src/example_input.txt b/2023/day3/src/example_input.txt new file mode 100644 index 0000000..624ea4f --- /dev/null +++ b/2023/day3/src/example_input.txt @@ -0,0 +1,10 @@ +467..114.. +...*...... +..35..633. +......#... +617*...... +.....+.58. +..592..... +......755. +...$.*.... +.664.598.. \ No newline at end of file diff --git a/2023/day3/src/main.rs b/2023/day3/src/main.rs index ec277e9..6bb6c82 100644 --- a/2023/day3/src/main.rs +++ b/2023/day3/src/main.rs @@ -1,10 +1,223 @@ +use crate::ValueType::Number; +use std::collections::HashSet; +use std::ops::Range; + const FILE_STR: &'static str = include_str!("input.txt"); +#[derive(Debug, Default)] +struct NumberLocation { + start: usize, + end: usize, + digits: String, +} + +impl NumberLocation { + fn new(start: usize, value: char) -> Self { + NumberLocation { + start, + end: start + 1, + digits: String::from(value), + } + } + + fn add_digit(&mut self, digit: char) { + self.end += 1; + self.digits.push(digit); + } + + fn to_number(&self) -> usize { + self.digits.parse::().unwrap() + } +} + +#[derive(Debug, Copy, Clone, PartialOrd, PartialEq, Eq, Hash)] +enum ValueType { + Number(usize), + Symbol, + Empty, +} + +#[derive(Debug, Default)] +struct Grid { + line_size: usize, + numbers: Vec, + values: Vec, +} + +impl Grid { + pub fn parse(input_str: &str) -> Self { + let len = input_str.split('\n').next().unwrap().len(); + let mut values: Vec = Vec::new(); + let mut numbers: Vec = Vec::new(); + let chars: Vec = input_str.replace("\n", "").chars().collect(); + let mut prev = ValueType::Empty; + chars.iter().enumerate().for_each(|(i, ch)| { + if ch.is_digit(10) { + if let Number(_) = prev { + let final_num = { numbers.len() - 1 }; + numbers.get_mut(final_num).unwrap().add_digit(*ch); + } else { + let nl = NumberLocation::new(i, *ch); + numbers.push(nl); + } + + prev = Number(numbers.len() - 1); + } else if *ch == '.' { + prev = ValueType::Empty; + } else { + prev = ValueType::Symbol; + } + + values.push(prev); + }); + + Grid { + line_size: len, + numbers, + values, + } + } + + pub fn num_cols(&self) -> usize { + self.line_size + } + + pub fn num_rows(&self) -> usize { + self.values.len() / self.num_cols() + } + + // Convert x,y coordinate into linear array index + pub fn xy_idx(&self, x: usize, y: usize) -> usize { + (y * self.num_cols()) + x + } + + /// Convert linear array index to x,y coordinate + pub fn idx_xy(&self, idx: usize) -> (usize, usize) { + (idx % self.num_cols(), idx / self.num_cols()) + } + + fn adjacent_ind(&self, i: usize) -> HashSet { + let mut ind = HashSet::new(); + if i >= self.values.len() { + return ind; + } + + // Forwards/backwards + if i > 0 { + ind.insert(i - 1); + } + if i + 1 < self.values.len() { + ind.insert(i + 1); + } + + // Row above/below + let (col, row) = self.idx_xy(i); + if row + 1 < self.num_rows() { + ind.insert(self.xy_idx(col, row + 1)); + } + if row > 0 { + ind.insert(self.xy_idx(col, row - 1)); + } + + // Diagonals (x+1, y-1),(x-1, y-1),(x+1, y+1),(x-1,y+1) + if col > 0 && row > 0 { + ind.insert(self.xy_idx(col - 1, row - 1)); + } + if col > 0 && row + 1 < self.num_rows() { + ind.insert(self.xy_idx(col - 1, row + 1)); + } + if col + 1 < self.num_cols() && row > 0 { + ind.insert(self.xy_idx(col + 1, row - 1)); + } + if col + 1 < self.num_cols() && row + 1 < self.num_rows() { + ind.insert(self.xy_idx(col + 1, row + 1)); + } + + ind + } + + fn find_adjacent(&self, r: Range) -> Vec { + let mut adj = Vec::new(); + + for i in r { + self.adjacent_ind(i) + .into_iter() + .for_each(|ind| adj.push(self.values[ind])); + } + + adj + } + + fn is_part_number(&self, r: Range) -> bool { + self.find_adjacent(r).contains(&ValueType::Symbol) + } + + fn get_part_numbers(&self) -> Vec { + self.numbers + .iter() + .filter(|nl| { + let range = nl.start..nl.end; + self.is_part_number(range) + }) + .map(|nl| nl.to_number()) + .collect() + } + + fn get_part_number_sum(&self) -> usize { + self.numbers + .iter() + .filter(|nl| { + let range = nl.start..nl.end; + self.is_part_number(range) + }) + .map(|nl| nl.to_number()) + .sum() + } +} + +fn part_one() { + let grid = Grid::parse(FILE_STR); + println!( + "Part 1: Sum of part numbers: {}", + grid.get_part_number_sum() + ); +} + fn main() { - println!("Hello, world!"); + part_one(); } #[cfg(test)] mod tests { + use std::iter::FromIterator; + + const EXAMPLE_FILE_STR: &'static str = include_str!("example_input.txt"); use super::*; -} \ No newline at end of file + + #[test] + fn test_adjacent_index() { + let grid = Grid::parse(EXAMPLE_FILE_STR); + assert_eq!(grid.adjacent_ind(5), HashSet::from([4, 6, 14, 15, 16])); + } + + #[test] + fn test_find_adjacent() { + let grid = Grid::parse(EXAMPLE_FILE_STR); + let adjacent = grid.find_adjacent(5..6); + let hadj = HashSet::from_iter(adjacent.iter().cloned()); + assert_eq!(hadj, HashSet::from([ValueType::Empty, Number(1)])); + } + + #[test] + fn test_get_part_numbers() { + let grid = Grid::parse(EXAMPLE_FILE_STR); + let expected = [467, 35, 633, 617, 592, 755, 664, 598]; + assert_eq!(grid.get_part_numbers(), expected); + } + + #[test] + fn test_part_number_sum() { + let grid = Grid::parse(EXAMPLE_FILE_STR); + assert_eq!(grid.get_part_number_sum(), 4361); + } +}