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

87
Cargo.lock generated
View file

@ -79,14 +79,28 @@ dependencies = [
name = "aoc_2024" name = "aoc_2024"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"arrayvec",
"bstr", "bstr",
"clap", "clap",
"color-eyre", "color-eyre",
"humantime", "humantime",
"itertools", "itertools",
"num",
"regex", "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]] [[package]]
name = "backtrace" name = "backtrace"
version = "0.3.71" version = "0.3.71"
@ -283,6 +297,79 @@ dependencies = [
"adler", "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]] [[package]]
name = "object" name = "object"
version = "0.32.2" version = "0.32.2"

View file

@ -5,9 +5,11 @@ authors = ["traxys <quentin@familleboyer.net>"]
edition = "2021" edition = "2021"
[dependencies] [dependencies]
arrayvec = "0.7.6"
bstr = "1.11.0" bstr = "1.11.0"
clap = { version = "4.5.21", features = ["derive"] } clap = { version = "4.5.21", features = ["derive"] }
color-eyre = "0.6.3" color-eyre = "0.6.3"
humantime = "2.1.0" humantime = "2.1.0"
itertools = "0.13.0" itertools = "0.13.0"
num = "0.4.3"
regex = "1.11.1" regex = "1.11.1"

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);
}
}