use itertools::Itertools; use std::fmt::Debug; use std::fs::File; use std::io::{BufRead, BufReader, Lines}; use std::ops::Index; use std::time::Instant; // BOILERPLATE type InputIter = Lines>; fn get_input() -> InputIter { let f = File::open("input").unwrap(); let br = BufReader::new(f); br.lines() } fn main() { let start = Instant::now(); let ans1 = problem1(get_input()); let duration = start.elapsed(); println!("Problem 1 solution: {} [{}s]", ans1, duration.as_secs_f64()); let start = Instant::now(); let ans2 = problem2(get_input()); let duration = start.elapsed(); println!("Problem 2 solution: {} [{}s]", ans2, duration.as_secs_f64()); } // Parse #[derive(Clone, Copy)] struct Vec3 { x: f64, y: f64, z: f64, } impl Debug for Vec3 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}, {}, {}", self.x, self.y, self.z) } } #[derive(Clone, Copy)] struct Vec2 { x: f64, y: f64, } impl Debug for Vec2 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}, {}", self.x, self.y) } } #[derive(Clone)] struct Hailstone { pos: Vec3, vel: Vec3, } impl Hailstone { fn xy_vel_angle(&self) -> f64 { (self.vel.y / self.vel.x).atan() } fn xy_vel_abs(&self) -> f64 { (self.vel.x.powi(2) + self.vel.y.powi(2)).sqrt() } fn xy_slope(&self) -> f64 { self.vel.y / self.vel.x } fn y_crossing(&self) -> f64 { self.pos.y - self.xy_slope() * self.pos.x } fn xy_poi(&self, other: &Hailstone) -> Option { let our_slope = self.xy_slope(); let other_slope = other.xy_slope(); if our_slope == other_slope || our_slope * other_slope == -1. { None } else { let our_yint = self.y_crossing(); let other_yint = other.y_crossing(); let ratio = (other_yint - our_yint) / (our_slope - other_slope); Some(Vec2 { x: ratio, y: our_slope * ratio + our_yint, }) } } fn xy_point_future(&self, point: &Vec2) -> bool { // a point will be in the past if the difference between its 'new' position and its 'start' position has a // different sign than the velocity for any component let diffs = [point.x - self.pos.x, point.y - self.pos.y]; // for (diff, vel) in diffs.iter().zip([self.vel.x, self.vel.y].iter()) { // println!(" diff: {:?} vel: {:?} mul: {:?}", diff, vel, diff * vel > 0.); // } diffs.iter().zip([self.vel.x, self.vel.y].iter()).any(|(diff, vel)| diff * vel > 0.) } } impl From<&str> for Hailstone { fn from(value: &str) -> Self { let (pos, vel) = value.split_once("@").unwrap(); let [px, py, pz] = pos.split(",").map(|s| s.trim().parse::().unwrap()).collect_vec()[..] else { panic!() }; let [vx, vy, vz] = vel.split(",").map(|s| s.trim().parse::().unwrap()).collect_vec()[..] else { panic!() }; Self { pos: Vec3 { x: px, y: py, z: pz }, vel: Vec3 { x: vx, y: vy, z: vz }, } } } impl Debug for Hailstone { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{:?} @ {:?}", self.pos, self.vel) } } #[derive(Debug)] struct Hailstones { stones: Vec, } impl Hailstones { fn count_xy_pois(&self, bounds: (f64, f64)) -> u64 { assert!(bounds.0 < bounds.1); let mut stones = self.stones.clone(); let mut count = 0; while let Some(stone) = &stones.pop() { for other in &stones { let Some(poi) = stone.xy_poi(other) else { continue }; // println!("intersection: {:?} / {:?} @ {:?}", stone, other, poi); if poi.x >= bounds.0 && poi.x <= bounds.1 && poi.y >= bounds.0 && poi.y <= bounds.1 && stone.xy_point_future(&poi) && other.xy_point_future(&poi) { count += 1; } } } count } } impl Index for Hailstones { type Output = Hailstone; fn index(&self, index: usize) -> &Self::Output { &self.stones[index] } } impl From> for Hailstones { fn from(value: Lines) -> Self { let mut stones = Vec::new(); for line in value { stones.push(Hailstone::from(line.unwrap().as_str())); } Hailstones { stones } } } // PROBLEM 1 solution fn problem1(input: Lines) -> u64 { problem1_impl(input, (200000000000000., 400000000000000.)) } fn problem1_impl(input: Lines, bounds: (f64, f64)) -> u64 { let stones = Hailstones::from(input); stones.count_xy_pois(bounds) } // PROBLEM 2 solution fn problem2(input: Lines) -> u64 { 0 } #[cfg(test)] mod tests { use crate::*; use std::io::Cursor; const EXAMPLE: &str = &"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"; #[test] fn problem1_example() { let c = Cursor::new(EXAMPLE); assert_eq!(problem1_impl(c.lines(), (7., 27.)), 2); } #[test] fn problem2_example() { let c = Cursor::new(EXAMPLE); assert_eq!(problem2(c.lines()), 0); } }