use std::{ collections::{HashMap, HashSet}, convert::Infallible, str::FromStr, }; use gcd::Gcd; use itertools::Itertools; pub fn day8_part1(input: &str) -> String { let city: City = input.parse().unwrap(); let antinodes: HashSet = city.antinodes().collect(); antinodes.len().to_string() } pub fn day8_part2(input: &str) -> String { let city: City = input.parse().unwrap(); let antinodes: HashSet = city.resonant_antinodes().collect(); antinodes.len().to_string() } fn antinodes(antenna1: Point, antenna2: Point) -> (Option, Option) { let distance: Vector2 = (antenna1, antenna2).into(); let antinode1 = antenna2 + distance; let antinode2 = antenna1 - distance; (antinode1, antinode2) } fn resonant_antinodes(antenna1: Point, antenna2: Point, width: usize, height: usize) -> Vec { let distance: Vector2 = (antenna1, antenna2).into(); let distance = distance.simplified(); let mut last_antinode = antenna1; let mut antinodes = vec![last_antinode]; while let Some(next_antinode) = last_antinode + distance { if next_antinode.row >= height || next_antinode.col >= width { break; } else { antinodes.push(next_antinode); last_antinode = next_antinode; } } let mut last_antinode = antenna1; while let Some(next_antinode) = last_antinode - distance { if next_antinode.row >= height || next_antinode.col >= width { break; } else { antinodes.push(next_antinode); last_antinode = next_antinode; } } antinodes } #[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash, Debug)] struct Point { row: usize, col: usize, } impl std::ops::Add for Point { type Output = Option; fn add(self, rhs: Vector2) -> Self::Output { let row = self.row as isize + rhs.row_distance; let col = self.col as isize + rhs.col_distance; if row >= 0 && col >= 0 { Some(Self { row: row as usize, col: col as usize, }) } else { None } } } impl std::ops::Sub for Point { type Output = Option; fn sub(self, rhs: Vector2) -> Self::Output { let row = self.row as isize - rhs.row_distance; let col = self.col as isize - rhs.col_distance; if row >= 0 && col >= 0 { Some(Self { row: row as usize, col: col as usize, }) } else { None } } } #[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash, Debug)] struct Vector2 { row_distance: isize, col_distance: isize, } impl From<(Point, Point)> for Vector2 { fn from((start, end): (Point, Point)) -> Self { Self { row_distance: end.row as isize - start.row as isize, col_distance: end.col as isize - start.col as isize, } } } impl Vector2 { fn simplified(&self) -> Self { let mut retval = *self; let pos_col_distance: usize = retval.col_distance.unsigned_abs(); let pos_row_distance: usize = retval.row_distance.unsigned_abs(); let gcd = pos_col_distance.gcd(pos_row_distance); if gcd != 1 { retval.col_distance /= gcd as isize; retval.row_distance /= gcd as isize; } retval } } #[derive(Debug)] struct City { width: usize, height: usize, antenna_groups: Vec>, inner: Vec>>, } impl FromStr for City { type Err = Infallible; fn from_str(s: &str) -> Result { let inner = s .lines() .map(|line| { line.chars() .map(|c| if c == '.' || c == '#' { None } else { Some(c) }) .collect() }) .collect_vec(); Ok(Self { antenna_groups: Self::make_antenna_groups(&inner), inner, width: s.lines().next().unwrap().len(), height: s.lines().count(), }) } } impl City { fn make_antenna_groups(inner: &[Vec>]) -> Vec> { let mut retval: HashMap> = HashMap::new(); for row in 0..inner.len() { for col in 0..inner[0].len() { let cell = inner[row][col]; let Some(cell) = cell else { continue }; retval.entry(cell).or_default().push(Point { row, col }) } } retval.into_values().collect() } fn antenna_pairs(&self) -> impl Iterator { self .antenna_groups .iter() .flat_map(|group| group.iter().combinations(2)) .map(|combination| (*combination[0], *combination[1])) } fn antinodes(&self) -> impl Iterator { self.antenna_pairs() .map(|(from, to)| antinodes(from, to)) .flat_map(|(a1, a2)| [a1, a2]) .flatten() .filter(|p| p.row < self.height && p.col < self.width) } fn resonant_antinodes(&self) -> impl Iterator { self.antenna_pairs() .flat_map(|(from, to)| resonant_antinodes(from, to, self.width, self.height)) .filter(|p| p.row < self.height && p.col < self.width) } }