day(24): add solution
Signed-off-by: Matej Focko <me@mfocko.xyz>
This commit is contained in:
parent
40721c4d56
commit
a61866c12e
2 changed files with 182 additions and 0 deletions
5
samples/day24.txt
Normal file
5
samples/day24.txt
Normal file
|
@ -0,0 +1,5 @@
|
|||
19, 13, 30 @ -2, 1, -2
|
||||
18, 19, 22 @ -1, -1, -2
|
||||
20, 25, 34 @ -2, -2, -4
|
||||
12, 31, 28 @ -1, -2, -1
|
||||
20, 19, 15 @ 1, -5, -3
|
177
src/bin/day24.rs
Normal file
177
src/bin/day24.rs
Normal file
|
@ -0,0 +1,177 @@
|
|||
use std::ops::RangeInclusive;
|
||||
|
||||
use aoc_2023::*;
|
||||
|
||||
type Output1 = u32;
|
||||
type Output2 = i128;
|
||||
|
||||
type Vec3D = Vector3D<i128>;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct FloatingHailstone {
|
||||
position: Vector3D<f64>,
|
||||
velocity: Vector3D<f64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Hailstone {
|
||||
position: Vec3D,
|
||||
velocity: Vec3D,
|
||||
}
|
||||
|
||||
impl Hailstone {
|
||||
fn as_float(&self) -> FloatingHailstone {
|
||||
FloatingHailstone {
|
||||
position: Vector3D::<f64>::new(
|
||||
self.position.x() as f64,
|
||||
self.position.y() as f64,
|
||||
self.position.z() as f64,
|
||||
),
|
||||
velocity: Vector3D::<f64>::new(
|
||||
self.velocity.x() as f64,
|
||||
self.velocity.y() as f64,
|
||||
self.velocity.z() as f64,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Hailstone> for (Vec3D, Vec3D) {
|
||||
fn from(value: Hailstone) -> Self {
|
||||
(value.position, value.velocity)
|
||||
}
|
||||
}
|
||||
|
||||
struct Day24 {
|
||||
hailstones: Vec<Hailstone>,
|
||||
range: RangeInclusive<i128>,
|
||||
}
|
||||
impl Solution<Output1, Output2> for Day24 {
|
||||
fn new<P: AsRef<Path>>(pathname: P) -> Self {
|
||||
let lines: Vec<String> = file_to_lines(pathname);
|
||||
|
||||
let hailstones = lines
|
||||
.into_iter()
|
||||
.map(|l| {
|
||||
let coords = l
|
||||
.split(&[',', '@'])
|
||||
.map(|c| c.trim().parse().unwrap())
|
||||
.collect_vec();
|
||||
|
||||
Hailstone {
|
||||
position: Vec3D::new(coords[0], coords[1], coords[2]),
|
||||
velocity: Vec3D::new(coords[3], coords[4], coords[5]),
|
||||
}
|
||||
})
|
||||
.collect_vec();
|
||||
|
||||
let count = hailstones.len();
|
||||
|
||||
Self {
|
||||
hailstones,
|
||||
range: if count == 5 {
|
||||
5..=30 // small input
|
||||
} else {
|
||||
200000000000000..=400000000000000
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn part_1(&mut self) -> Output1 {
|
||||
let mut result = 0;
|
||||
|
||||
for (index, fst_h) in self.hailstones[1..].iter().enumerate() {
|
||||
let (a, b) = (fst_h.position.x(), fst_h.position.y());
|
||||
let (c, d) = (fst_h.velocity.x(), fst_h.velocity.y());
|
||||
|
||||
for snd_h in &self.hailstones[..index + 1] {
|
||||
let (e, f) = (snd_h.position.x(), snd_h.position.y());
|
||||
let (g, h) = (snd_h.velocity.x(), snd_h.velocity.y());
|
||||
|
||||
// If the determinant is zero there is no solution possible
|
||||
// which implies the trajectories are parallel.
|
||||
let determinant = d * g - c * h;
|
||||
if determinant == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Invert the 2x2 matrix then multiply by the respective columns to find the times.
|
||||
let t = (g * (f - b) - h * (e - a)) / determinant;
|
||||
let u = (c * (f - b) - d * (e - a)) / determinant;
|
||||
|
||||
// We can pick either the first or second hailstone to find the intersection position.
|
||||
let x = a + t * c;
|
||||
let y = b + t * d;
|
||||
|
||||
// Both times must be in the future and the position within the specified area.
|
||||
if t >= 0 && u >= 0 && self.range.contains(&x) && self.range.contains(&y) {
|
||||
result += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn part_2(&mut self) -> Output2 {
|
||||
const BRUTE_RANGE: RangeInclusive<i128> = -1000..=1000;
|
||||
|
||||
let mut possible_x_vel = Vec::new();
|
||||
let mut possible_y_vel = Vec::new();
|
||||
let mut possible_z_vel = Vec::new();
|
||||
|
||||
let mut iter = self.hailstones.iter().tuple_combinations();
|
||||
while possible_x_vel.len() != 1 || possible_y_vel.len() != 1 || possible_z_vel.len() != 1 {
|
||||
let (a, b) = iter.next().expect("No solution found");
|
||||
let process = |possible: &mut Vec<i128>, idx: usize| {
|
||||
let pos = (a.position.as_slice()[idx], b.position.as_slice()[idx]);
|
||||
let vel = (a.velocity.as_slice()[idx], b.velocity.as_slice()[idx]);
|
||||
|
||||
if vel.0 != vel.1 {
|
||||
return;
|
||||
}
|
||||
|
||||
let delta = (pos.0 - pos.1).abs();
|
||||
let this = BRUTE_RANGE
|
||||
.clone()
|
||||
.filter(|i| i != &vel.0 && delta % (i - vel.0) == 0)
|
||||
.collect_vec();
|
||||
|
||||
possible.retain(|v| this.contains(v));
|
||||
if possible.is_empty() {
|
||||
possible.extend(this);
|
||||
}
|
||||
};
|
||||
|
||||
process(&mut possible_x_vel, 0);
|
||||
process(&mut possible_y_vel, 1);
|
||||
process(&mut possible_z_vel, 2);
|
||||
}
|
||||
|
||||
let (a, b) = (self.hailstones[0].as_float(), self.hailstones[1].as_float());
|
||||
let (xv, yv, zv) = (
|
||||
possible_x_vel[0] as f64,
|
||||
possible_y_vel[0] as f64,
|
||||
possible_z_vel[0] as f64,
|
||||
);
|
||||
|
||||
let ma = (a.velocity.y() - yv) / (a.velocity.x() - xv);
|
||||
let mb = (b.velocity.y() - yv) / (b.velocity.x() - xv);
|
||||
|
||||
let ca = a.position.y() - ma * a.position.x();
|
||||
let cb = b.position.y() - mb * b.position.x();
|
||||
|
||||
let x = (cb - ca) / (ma - mb);
|
||||
let y = ma * x + ca;
|
||||
let t = (x - a.position.x()) / (a.velocity.x() - xv);
|
||||
let z = a.position.z() + (a.velocity.z() - zv) * t;
|
||||
|
||||
((x + y + z) as i64).into()
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
Day24::main()
|
||||
}
|
||||
|
||||
test_sample!(day_24, Day24, 2, 47);
|
Loading…
Reference in a new issue