From cc1630d7416e8119455bbfdaa422992ad9e2b6c5 Mon Sep 17 00:00:00 2001 From: Quentin Boyer Date: Sun, 8 Dec 2024 21:23:54 +0100 Subject: [PATCH] Day 8 --- Cargo.lock | 87 ++++++++++++++ Cargo.toml | 2 + src/bin/day8.rs | 313 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 402 insertions(+) create mode 100644 src/bin/day8.rs diff --git a/Cargo.lock b/Cargo.lock index 9706f70..544f4d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -79,14 +79,28 @@ dependencies = [ name = "aoc_2024" version = "0.1.0" dependencies = [ + "arrayvec", "bstr", "clap", "color-eyre", "humantime", "itertools", + "num", "regex", ] +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + [[package]] name = "backtrace" version = "0.3.71" @@ -283,6 +297,79 @@ dependencies = [ "adler", ] +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "object" version = "0.32.2" diff --git a/Cargo.toml b/Cargo.toml index 8978658..31d30c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,9 +5,11 @@ authors = ["traxys "] edition = "2021" [dependencies] +arrayvec = "0.7.6" bstr = "1.11.0" clap = { version = "4.5.21", features = ["derive"] } color-eyre = "0.6.3" humantime = "2.1.0" itertools = "0.13.0" +num = "0.4.3" regex = "1.11.1" diff --git a/src/bin/day8.rs b/src/bin/day8.rs new file mode 100644 index 0000000..47fc17a --- /dev/null +++ b/src/bin/day8.rs @@ -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>); + +#[inline(never)] +pub fn parsing(input: &BString) -> color_eyre::Result { + 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 { + 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 { + 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) { + 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 { + 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 { + 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 { + 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::>(); + + let (lines, columns, antennas) = super::parsing(&initial).unwrap(); + let poles = super::antipoles(&antennas[&b'a'], lines, columns); + + assert_eq!(poles, expected_poles); + } +}