Day 8
This commit is contained in:
parent
585eefd437
commit
cc1630d741
3 changed files with 402 additions and 0 deletions
87
Cargo.lock
generated
87
Cargo.lock
generated
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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
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