diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/bin/day_12.rs | 158 |
1 files changed, 158 insertions, 0 deletions
diff --git a/src/bin/day_12.rs b/src/bin/day_12.rs new file mode 100644 index 0000000..678653b --- /dev/null +++ b/src/bin/day_12.rs @@ -0,0 +1,158 @@ +use rpds::vector::Vector; +use std::fmt; +use std::io; +use std::io::prelude::*; +use std::iter; +use std::num::ParseIntError; +use std::ops; +use std::process; +use std::str::FromStr; +use structopt::StructOpt; + +#[derive(Debug, StructOpt)] +#[structopt(name = "Day 12: The N-Body Problem")] +/// Simulates N bodies, physically interacting +/// +/// See https://adventofcode.com/2019/day/12 for details. +struct Opt { + #[structopt(short = "n")] + n: usize, +} + +fn main() { + let stdin = io::stdin(); + let opt = Opt::from_args(); + + let planets: Vec<Planet> = stdin + .lock() + .lines() + .map(|x| exit_on_failed_assertion(x, "Error reading input")) + .map(|x| exit_on_failed_assertion(x.parse::<Planet>(), "Input was not a valid planet")) + .collect(); + + println!("{}", energy(simulate_planets_n_iterations(planets, opt.n))); +} + +fn exit_on_failed_assertion<A, E: std::error::Error>(data: Result<A, E>, message: &str) -> A { + match data { + Ok(data) => data, + Err(e) => { + eprintln!("{}: {}", message, e); + process::exit(1); + } + } +} + +fn energy(planets: Vec<Planet>) -> i32 { + planets.into_iter().map(|p| p.energy()).sum() +} + +fn simulate_planets_n_iterations(planets: Vec<Planet>, n: usize) -> Vec<Planet> { + iter::successors(Some((0, planets)), |(i, planets)| { + Some((i + 1, simulate_planets(planets.clone()))) + }) + .find(|(i, _)| *i == n) + .unwrap() + .1 +} + +fn simulate_planets(planets: Vec<Planet>) -> Vec<Planet> { + simulate_velocity(simulate_gravity(planets)) +} + +fn simulate_gravity(planets: Vec<Planet>) -> Vec<Planet> { + planets + .iter() + .map(|p| Planet { + pos: p.pos.clone(), + vel: planets + .iter() + .filter(|o| p != *o) + .map(|o| p.gravity_pull(o)) + .fold(p.vel, |acc, next| acc + next), + }) + .collect() +} + +fn simulate_velocity(planets: Vec<Planet>) -> Vec<Planet> { + planets + .into_iter() + .map(|p| Planet { + pos: p.pos + p.vel, + vel: p.vel, + }) + .collect() +} + +#[derive(Debug, Default, Clone, PartialEq)] +struct Planet { + pos: Vec3d, + vel: Vec3d, +} + +#[derive(Debug, Default, Clone, Copy, PartialEq)] +struct Vec3d { + x: i32, + y: i32, + z: i32, +} + +impl FromStr for Planet { + type Err = ParseIntError; + + fn from_str(s: &str) -> Result<Self, ParseIntError> { + s.replace(|c| "<>xyz= ".contains(c), "") + .split(',') + .map(|i| i.parse::<i32>()) + .collect::<Result<Vec<_>, _>>() + .and_then(|v| match &v[..] { + [x, y, z] => Ok(Planet { + pos: Vec3d { + x: *x, + y: *y, + z: *z, + }, + vel: Vec3d::default(), + }), + _ => "wrong number of fields" + .parse::<i32>() + .map(|x| Planet::default()), + }) + } +} + +impl Planet { + fn gravity_pull(&self, other: &Planet) -> Vec3d { + Vec3d { + x: gravity(self.pos.x, other.pos.x), + y: gravity(self.pos.y, other.pos.y), + z: gravity(self.pos.z, other.pos.z), + } + } + + fn energy(&self) -> i32 { + (self.pos.x.abs() + self.pos.y.abs() + self.pos.z.abs()) + * (self.vel.x.abs() + self.vel.y.abs() + self.vel.z.abs()) + } +} + +fn gravity(this: i32, that: i32) -> i32 { + if that > this { + 1 + } else if this > that { + -1 + } else { + 0 + } +} + +impl ops::Add<Vec3d> for Vec3d { + type Output = Vec3d; + fn add(self, rhs: Vec3d) -> Self::Output { + Vec3d { + x: self.x + rhs.x, + y: self.y + rhs.y, + z: self.z + rhs.z, + } + } +} |