Day 8
This commit is contained in:
parent
585eefd437
commit
cc1630d741
3 changed files with 402 additions and 0 deletions
313
src/bin/day8.rs
Normal file
313
src/bin/day8.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue