advent-of-code/2023/day3/src/main.rs

240 lines
6.0 KiB
Rust

use std::collections::HashSet;
use std::ops::Range;
const FILE_STR: &'static str = include_str!("input.txt");
#[derive(Debug, Default, Clone)]
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::<usize>().unwrap()
}
}
#[derive(Debug, Copy, Clone, PartialOrd, PartialEq, Eq, Hash)]
enum ValueType {
Number(usize),
Symbol(char),
Err(char),
Empty,
}
#[derive(Debug, Default, Clone)]
struct Grid {
line_size: usize,
numbers: Vec<NumberLocation>,
values: Vec<ValueType>,
}
impl Grid {
pub fn parse(input_str: &str) -> Self {
use ValueType::*;
let len = input_str.split('\n').next().unwrap().len();
let chars: Vec<char> = input_str.replace("\n", "").chars().collect();
let mut values: Vec<ValueType> = Vec::new();
let mut numbers: Vec<NumberLocation> = Vec::new();
let mut prev = Empty;
let symbols = vec!['%', '&', '/', '*', '=', '+', '#', '@', '-', '$'];
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 = Empty;
} else if symbols.contains(ch) {
prev = Symbol(*ch);
} else {
prev = Err(*ch);
}
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<usize> {
let mut ind = HashSet::new();
if i >= self.values.len() {
return ind;
}
let (col, row) = self.idx_xy(i);
// Forwards/backwards
if col > 0 {
ind.insert(self.xy_idx(col - 1, row));
}
if col + 1 < self.num_cols() {
ind.insert(self.xy_idx(col + 1, row));
}
// Row above/below
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<usize>) -> Vec<ValueType> {
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<usize>) -> bool {
use ValueType::*;
self.find_adjacent(r).into_iter().any(|t| match t {
Symbol(_) => true,
_ => false,
})
}
fn get_part_numbers(&self) -> Vec<usize> {
self.numbers
.iter()
.filter(|nl| {
let range = nl.start..nl.end;
self.is_part_number(range)
})
.map(|nl| nl.to_number())
.collect()
}
fn get_unique_part_numbers(&self) -> HashSet<usize> {
HashSet::from_iter(self.get_part_numbers().iter().cloned())
}
fn get_part_number_sum(&self) -> usize {
self.get_unique_part_numbers().iter().sum()
}
}
fn part_one() {
let grid = Grid::parse(FILE_STR);
println!(
"Part 1: Sum of part numbers: {}",
grid.get_part_number_sum()
);
}
fn main() {
part_one();
}
#[cfg(test)]
mod tests {
const EXAMPLE_FILE_STR: &'static str = include_str!("example_input.txt");
use super::*;
use std::iter::FromIterator;
use ValueType::*;
#[test]
fn test_get_part_number_sum_part_1() {
let grid = Grid::parse(FILE_STR);
assert!(grid.get_part_number_sum() < 1645975);
}
#[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([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);
}
}