142 lines
3.8 KiB
Rust
142 lines
3.8 KiB
Rust
|
use std::{collections::HashSet, str::FromStr};
|
||
|
|
||
|
pub fn run() {
|
||
|
let input = include_str!("../input/day3.txt");
|
||
|
// let input = r#"467..114..
|
||
|
// ...*......
|
||
|
// ..35..633.
|
||
|
// ......#...
|
||
|
// 617*......
|
||
|
// .....+.58.
|
||
|
// ..592.....
|
||
|
// ......755.
|
||
|
// ...$.*....
|
||
|
// .664.598.."#;
|
||
|
|
||
|
let grid: Grid = input.parse().unwrap();
|
||
|
let all_part_numbers = grid
|
||
|
.symbol_positions()
|
||
|
.into_iter()
|
||
|
.flat_map(|pos| grid.adjacent_numbers(pos))
|
||
|
.collect::<HashSet<_>>();
|
||
|
|
||
|
let parts_sum: u32 = all_part_numbers
|
||
|
.into_iter()
|
||
|
.map(|pos| grid.parse_number_starting_at(pos))
|
||
|
.sum();
|
||
|
dbg!(parts_sum);
|
||
|
|
||
|
let gears_sum: u32 = grid
|
||
|
.gear_positions()
|
||
|
.into_iter()
|
||
|
.map(|pos| grid.gear_ratio(pos))
|
||
|
.sum();
|
||
|
dbg!(gears_sum);
|
||
|
}
|
||
|
|
||
|
struct Grid {
|
||
|
chars: Vec<Vec<char>>,
|
||
|
}
|
||
|
|
||
|
impl FromStr for Grid {
|
||
|
type Err = Box<dyn std::error::Error>;
|
||
|
|
||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||
|
let chars = s
|
||
|
.lines()
|
||
|
.filter(|l| !l.is_empty())
|
||
|
.map(|l| l.trim().chars().collect::<Vec<_>>())
|
||
|
.collect::<Vec<_>>();
|
||
|
let count = chars[0].len();
|
||
|
for line in chars.iter().skip(1) {
|
||
|
assert_eq!(count, line.len());
|
||
|
}
|
||
|
Ok(Grid { chars })
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl Grid {
|
||
|
fn symbol_positions(&self) -> Vec<(usize, usize)> {
|
||
|
let mut positions = vec![];
|
||
|
for (row, line) in self.chars.iter().enumerate() {
|
||
|
for (col, c) in line.iter().enumerate() {
|
||
|
if c.is_ascii_digit() {
|
||
|
continue;
|
||
|
} else if *c == '.' {
|
||
|
continue;
|
||
|
} else {
|
||
|
positions.push((col, row));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
positions
|
||
|
}
|
||
|
|
||
|
fn adjacent_numbers(&self, (col, row): (usize, usize)) -> HashSet<(usize, usize)> {
|
||
|
let mut adjacent = HashSet::new();
|
||
|
for dc in -1isize..=1 {
|
||
|
for dr in -1isize..=1 {
|
||
|
if dc == 0 && dr == 0 {
|
||
|
continue;
|
||
|
}
|
||
|
let c = col as isize + dc;
|
||
|
let r = row as isize + dr;
|
||
|
if c < 0
|
||
|
|| c >= self.chars[0].len() as isize
|
||
|
|| r < 0
|
||
|
|| r >= self.chars.len() as isize
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
if let Some(pos) = self.start_position_of_number_containig((c as usize, r as usize))
|
||
|
{
|
||
|
adjacent.insert(pos);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
adjacent
|
||
|
}
|
||
|
|
||
|
fn start_position_of_number_containig(
|
||
|
&self,
|
||
|
(col, row): (usize, usize),
|
||
|
) -> Option<(usize, usize)> {
|
||
|
let line = &self.chars[row];
|
||
|
if !line[col].is_ascii_digit() {
|
||
|
return None;
|
||
|
}
|
||
|
let mut start = col;
|
||
|
while start > 0 && line[start - 1].is_ascii_digit() {
|
||
|
start -= 1;
|
||
|
}
|
||
|
Some((start, row))
|
||
|
}
|
||
|
|
||
|
fn parse_number_starting_at(&self, (col, row): (usize, usize)) -> u32 {
|
||
|
self.chars[row][col..]
|
||
|
.iter()
|
||
|
.take_while(|c| c.is_ascii_digit())
|
||
|
.collect::<String>()
|
||
|
.parse()
|
||
|
.unwrap()
|
||
|
}
|
||
|
|
||
|
fn gear_positions(&self) -> Vec<(usize, usize)> {
|
||
|
self.symbol_positions()
|
||
|
.into_iter()
|
||
|
.filter(|(col, row)| {
|
||
|
self.chars[*row][*col] == '*' && self.adjacent_numbers((*col, *row)).len() == 2
|
||
|
})
|
||
|
.collect()
|
||
|
}
|
||
|
|
||
|
fn gear_ratio(&self, pos: (usize, usize)) -> u32 {
|
||
|
let adjacent = self
|
||
|
.adjacent_numbers(pos)
|
||
|
.into_iter()
|
||
|
.map(|pos| self.parse_number_starting_at(pos))
|
||
|
.collect::<Vec<_>>();
|
||
|
adjacent[0] * adjacent[1]
|
||
|
}
|
||
|
}
|