diff --git a/samples/day24.txt b/samples/day24.txt new file mode 100644 index 0000000..cbe1492 --- /dev/null +++ b/samples/day24.txt @@ -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 \ No newline at end of file diff --git a/src/bin/day24.rs b/src/bin/day24.rs new file mode 100644 index 0000000..7ada5b5 --- /dev/null +++ b/src/bin/day24.rs @@ -0,0 +1,177 @@ +use std::ops::RangeInclusive; + +use aoc_2023::*; + +type Output1 = u32; +type Output2 = i128; + +type Vec3D = Vector3D; + +#[derive(Debug, Clone)] +struct FloatingHailstone { + position: Vector3D, + velocity: Vector3D, +} + +#[derive(Debug, Clone)] +struct Hailstone { + position: Vec3D, + velocity: Vec3D, +} + +impl Hailstone { + fn as_float(&self) -> FloatingHailstone { + FloatingHailstone { + position: Vector3D::::new( + self.position.x() as f64, + self.position.y() as f64, + self.position.z() as f64, + ), + velocity: Vector3D::::new( + self.velocity.x() as f64, + self.velocity.y() as f64, + self.velocity.z() as f64, + ), + } + } +} + +impl From for (Vec3D, Vec3D) { + fn from(value: Hailstone) -> Self { + (value.position, value.velocity) + } +} + +struct Day24 { + hailstones: Vec, + range: RangeInclusive, +} +impl Solution for Day24 { + fn new>(pathname: P) -> Self { + let lines: Vec = 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 = -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, 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);