aoc2023/24/src/main.rs

206 lines
5.4 KiB
Rust

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<BufReader<File>>;
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<Vec2> {
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::<f64>().unwrap()).collect_vec()[..] else {
panic!()
};
let [vx, vy, vz] = vel.split(",").map(|s| s.trim().parse::<f64>().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<Hailstone>,
}
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<usize> for Hailstones {
type Output = Hailstone;
fn index(&self, index: usize) -> &Self::Output {
&self.stones[index]
}
}
impl<T: BufRead> From<Lines<T>> for Hailstones {
fn from(value: Lines<T>) -> 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<T: BufRead>(input: Lines<T>) -> u64 {
problem1_impl(input, (200000000000000., 400000000000000.))
}
fn problem1_impl<T: BufRead>(input: Lines<T>, bounds: (f64, f64)) -> u64 {
let stones = Hailstones::from(input);
stones.count_xy_pois(bounds)
}
// PROBLEM 2 solution
fn problem2<T: BufRead>(input: Lines<T>) -> 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);
}
}