This commit is contained in:
Quentin Boyer 2024-12-08 21:23:54 +01:00
parent 585eefd437
commit cc1630d741
3 changed files with 402 additions and 0 deletions

313
src/bin/day8.rs Normal file
View file

@ -0,0 +1,313 @@
use std::{
collections::{HashMap, HashSet},
time::Instant,
};
use aoc_2024::{load, print_res};
use arrayvec::ArrayVec;
use bstr::{BString, ByteSlice};
use num::integer::gcd;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Point {
line: isize,
col: isize,
}
type Parsed = (usize, usize, HashMap<u8, Vec<Point>>);
#[inline(never)]
pub fn parsing(input: &BString) -> color_eyre::Result<Parsed> {
let mut columns = 0;
let mut lines = 0;
let mut antennas = HashMap::new();
for (line, line_data) in input.lines().enumerate() {
lines = line + 1;
columns = line_data.len();
for (col, &c) in line_data.iter().enumerate().filter(|&(_, &c)| c != b'.') {
antennas.entry(c).or_insert_with(Vec::new).push(Point {
line: line as isize,
col: col as isize,
});
}
}
Ok((lines, columns, antennas))
}
// offset = (b - a)
// a + offset = a + (b - a) = b
// b - offset = b - (b - a) = a
//
// antipoles are a - offset & b + offset
fn antipoles_pair(a: Point, b: Point, lines: usize, columns: usize) -> ArrayVec<Point, 2> {
let mut antipoles = ArrayVec::new();
let in_bounds = |coord: isize, max: isize| coord >= 0 && coord < max;
let point_in_bounds = |line: isize, col: isize| {
in_bounds(line, lines as isize) && in_bounds(col, columns as isize)
};
let line_offset = b.line - a.line;
let col_offset = b.col - a.col;
let line_anti_a = a.line - line_offset;
let col_anti_a = a.col - col_offset;
if point_in_bounds(line_anti_a, col_anti_a) {
antipoles.push(Point {
line: line_anti_a,
col: col_anti_a,
});
}
let line_anti_b = b.line + line_offset;
let col_anti_b = b.col + col_offset;
if point_in_bounds(line_anti_b, col_anti_b) {
antipoles.push(Point {
line: line_anti_b,
col: col_anti_b,
});
}
antipoles
}
fn antipoles(antennas: &[Point], lines: usize, columns: usize) -> HashSet<Point> {
let mut poles = HashSet::new();
for (i, &a) in antennas.iter().enumerate() {
if i == antennas.len() {
continue;
}
for &b in &antennas[i + 1..] {
poles.extend(antipoles_pair(a, b, lines, columns))
}
}
poles
}
#[allow(unused)]
fn display_grid((lines, columns, antennas): Parsed, antipoles: &HashSet<Point>) {
for line in 0..(lines as isize) {
for col in 0..(columns as isize) {
let mut antipole = antipoles.contains(&Point { line, col });
let freq = antennas
.iter()
.find(|(_, v)| v.contains(&Point { line, col }))
.map(|(&f, _)| f);
match (antipole, freq) {
(true, None) => print!("#"),
(true, Some(_)) => print!("*"),
(false, None) => print!("."),
(false, Some(l)) => print!("{}", l as char),
}
}
println!()
}
}
#[inline(never)]
pub fn part1((lines, columns, antennas): Parsed) {
let mut all_antipoles = HashSet::new();
for antennas in antennas.values() {
all_antipoles.extend(antipoles(antennas, lines, columns));
}
print_res!("Number of antipoles: {}", all_antipoles.len());
}
/// a * col + b * line + c = 0
#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
struct Line {
a: isize,
b: isize,
c: isize,
}
impl Line {
fn normalize(mut self) -> Self {
if self.a < 0 {
self.a *= -1;
self.b *= -1;
self.c *= -1;
}
let d = gcd(self.a, gcd(self.b, self.c));
self.a /= d;
self.b /= d;
self.c /= d;
self
}
fn from_points(a: Point, b: Point) -> Line {
Line {
a: a.line - b.line,
b: b.col - a.col,
c: a.col * b.line - b.col * a.line,
}
.normalize()
}
fn integer_points(&self, lines: usize, columns: usize) -> Vec<Point> {
if self.a == 0 {
todo!()
} else {
// We want a * col + b * line + c = 0
// a * col = - ( c + b * line)
// col = - ( c + b * line ) / a
let mut v = Vec::new();
for line in 0..(lines as isize) {
let up = -(self.c + self.b * line);
if up.rem_euclid(self.a) != 0 {
continue;
}
let col = up / self.a;
if col < 0 || col >= columns as isize {
continue;
}
v.push(Point { line, col });
}
v
}
}
}
fn antenna_lines(antennas: &[Point]) -> HashSet<Line> {
let mut lines = HashSet::new();
for (i, &a) in antennas.iter().enumerate() {
if i == antennas.len() {
continue;
}
for &b in &antennas[i + 1..] {
lines.insert(Line::from_points(a, b));
}
}
lines
}
#[inline(never)]
pub fn part2((lines, columns, antennas): Parsed) {
let mut all_antipoles = HashSet::new();
for antennas in antennas.values() {
let antipode_lines = antenna_lines(antennas);
for line in antipode_lines {
all_antipoles.extend(line.integer_points(lines, columns));
}
}
print_res!("Number of antipoles: {}", all_antipoles.len());
}
pub fn main() -> color_eyre::Result<()> {
let context = load()?;
let start = Instant::now();
let parsed = parsing(&context.input)?;
let elapsed = humantime::format_duration(start.elapsed());
let start = Instant::now();
if context.part == 1 {
part1(parsed);
} else {
part2(parsed);
}
let elapsed_part = humantime::format_duration(start.elapsed());
println!(" Parsing: {elapsed}");
println!(" Solving: {elapsed_part}");
Ok(())
}
#[cfg(test)]
mod test {
use std::collections::HashSet;
use bstr::BString;
use crate::{antipoles_pair, Point};
fn parse_antipoles(input: &str) -> Vec<super::Point> {
input
.lines()
.enumerate()
.flat_map(|(line, line_data)| {
line_data
.as_bytes()
.iter()
.enumerate()
.filter_map(move |(col, &c)| match c {
b'#' => Some(Point {
line: line as isize,
col: col as isize,
}),
_ => None,
})
})
.collect()
}
#[test]
fn two() {
let result = "\
..........
...#......
..........
....a.....
..........
.....a....
..........
......#...
..........
..........";
let initial = BString::from(result.replace('#', "."));
let expected_poles = parse_antipoles(result);
let (lines, columns, antennas) = super::parsing(&initial).unwrap();
let &[a, b] = &*antennas[&b'a'] else { panic!() };
let poles = antipoles_pair(a, b, lines, columns);
assert_eq!(poles.as_slice(), expected_poles.as_slice());
}
#[test]
fn three() {
let result = "\
..........
...#......
#.........
....a.....
........a.
.....a....
..#.......
......#...
..........
..........";
let initial = BString::from(result.replace('#', "."));
let expected_poles = parse_antipoles(result).into_iter().collect::<HashSet<_>>();
let (lines, columns, antennas) = super::parsing(&initial).unwrap();
let poles = super::antipoles(&antennas[&b'a'], lines, columns);
assert_eq!(poles, expected_poles);
}
}