use std::collections::{HashMap, HashSet};
use crate::solver::DaySolver;
use crate::grid::{Coordinate, Grid};
fn add_distance(coordinate: Coordinate, distance: (i64, i64)) -> Option<Coordinate> {
coordinate.try_add(distance)
}
fn sub_distance(coordinate: Coordinate, distance: (i64, i64)) -> Option<Coordinate> {
coordinate.try_sub(distance)
}
fn part2_possible_antinodes<F>(
grid: &Grid<Option<char>>,
coordinate: Coordinate,
distance: (i64, i64),
op: F,
mut accumulator: Vec<Coordinate>
) -> Vec<Coordinate>
where F: Fn(Coordinate, (i64, i64)) -> Option<Coordinate> {
match op(coordinate, distance).filter(|c| grid.get(*c).is_some()) {
None => accumulator,
Some(next_coord) => {
accumulator.push(next_coord);
part2_possible_antinodes(grid, next_coord, distance, op, accumulator)
}
}
}
trait Pairable<T> {
fn pairs(&self) -> Vec<(&T, &T)>;
}
impl<T> Pairable<T> for HashSet<T> {
fn pairs(&self) -> Vec<(&T, &T)> {
let v: Vec<&T> = self.iter().collect();
let mut p = vec![];
for i in 0..v.len() {
let thing1 = v[i];
for thing2 in &v[i+1..] {
p.push((thing1, *thing2));
}
}
p
}
}
fn parse_input(input: String) -> (Grid<Option<char>>, HashMap<char, HashSet<Coordinate>>) {
let g: Grid<Option<char>> =
input.lines()
.map(|line| line.chars()
.map(|c| if c == '.' {
None
} else {
Some(c)
}).collect::<Vec<Option<char>>>()
)
.collect::<Vec<Vec<Option<char>>>>()
.into();
let mut freq_to_coords: HashMap<char, HashSet<Coordinate>> = HashMap::new();
for (coord, freq_opt) in g.iter() {
match freq_opt {
None => (),
Some(freq) => {
freq_to_coords.entry(*freq)
.and_modify(|coords| {
coords.insert(coord);
})
.or_insert(HashSet::from([coord]));
}
}
}
(g, freq_to_coords)
}
pub struct Day08Solver;
impl DaySolver for Day08Solver {
fn part1(&self, input: String) -> usize {
let (g, freq_to_coords) = parse_input(input);
let mut antinodes: HashSet<Coordinate> = HashSet::new();
for (_, coords) in freq_to_coords {
// println!("Freq = {}", freq);
for (c1, c2) in coords.pairs() {
let distance = c1.xy_distance_to(c2);
let possible_antinodes: Vec<Coordinate> = [c1.try_sub(distance), c2.try_add(distance)].into_iter()
.flat_map(|co| co.filter(|c| g.get(*c).is_some()))
.collect();
// println!("Pair = ({},{}), antinodes = {:?}", c1, c2, possible_antinodes);
for antinode in possible_antinodes {
antinodes.insert(antinode);
}
}
}
antinodes.len()
}
fn part2(&self, input: String) -> usize {
let (g, freq_to_coords) = parse_input(input);
let mut antinodes: HashSet<Coordinate> = HashSet::new();
for (freq, coords) in freq_to_coords {
println!("Freq = {}", freq);
for (c1, c2) in coords.pairs() {
let distance = c1.xy_distance_to(c2);
let possible_antinodes: Vec<Coordinate> = [
part2_possible_antinodes(&g, *c1, distance, add_distance, vec![*c1]),
part2_possible_antinodes(&g, *c1, distance, sub_distance, vec![*c1]),
part2_possible_antinodes(&g, *c2, distance, add_distance, vec![*c2]),
part2_possible_antinodes(&g, *c2, distance, sub_distance, vec![*c2]),
].into_iter().flatten().collect();
println!("Pair = ({},{}), antinodes = {:?}", c1, c2, possible_antinodes);
for antinode in possible_antinodes {
antinodes.insert(antinode);
}
}
}
antinodes.len()
}
}