summaryrefslogtreecommitdiff
path: root/2019/src/bin/day_12.rs
diff options
context:
space:
mode:
Diffstat (limited to '2019/src/bin/day_12.rs')
-rw-r--r--2019/src/bin/day_12.rs205
1 files changed, 205 insertions, 0 deletions
diff --git a/2019/src/bin/day_12.rs b/2019/src/bin/day_12.rs
new file mode 100644
index 0000000..7e42f4c
--- /dev/null
+++ b/2019/src/bin/day_12.rs
@@ -0,0 +1,205 @@
+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: Option<u64>,
+}
+
+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();
+
+ match opt.n {
+ Some(n) => println!("{}", energy(simulate_planets_n_iterations(planets, n))),
+ None => println!("{}", simulate_planets_to_duplicate(planets)),
+ };
+}
+
+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: u64) -> Vec<Planet> {
+ simulate_planets_iter(planets)
+ .find(|(i, _)| *i == n)
+ .unwrap()
+ .1
+}
+
+fn simulate_planets_to_duplicate(planets: Vec<Planet>) -> u64 {
+ lowest_common_multiple(
+ simulate_planets_to_duplicate_1d(planets.clone(), |(p, o)| p.equal_x(o)),
+ simulate_planets_to_duplicate_1d(planets.clone(), |(p, o)| p.equal_y(o)),
+ simulate_planets_to_duplicate_1d(planets.clone(), |(p, o)| p.equal_z(o)),
+ )
+}
+
+fn simulate_planets_to_duplicate_1d(
+ planets: Vec<Planet>,
+ eq: impl FnMut((&Planet, &Planet)) -> bool + Copy,
+) -> u64 {
+ simulate_planets_iter(planets.clone())
+ .skip(1)
+ .find(|(_i, ps)| ps.iter().zip(planets.iter()).all(eq))
+ .unwrap()
+ .0
+}
+
+fn lowest_common_multiple(x: u64, y: u64, z: u64) -> u64 {
+ (1..)
+ .map(|i| x * i)
+ .find(|mx| multiples(y, *mx) && multiples(z, *mx))
+ .unwrap()
+}
+
+fn multiples(x: u64, target: u64) -> bool {
+ target % x == 0
+}
+
+fn simulate_planets_iter(planets: Vec<Planet>) -> impl Iterator<Item = (u64, Vec<Planet>)> {
+ iter::successors(Some((0, planets.clone())), |(i, planets)| {
+ Some((i + 1, simulate_planets(planets.clone())))
+ })
+}
+
+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.acc(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 acc(&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 equal_x(&self, other: &Planet) -> bool {
+ self.pos.x == other.pos.x && self.vel.x == other.vel.x
+ }
+
+ fn equal_y(&self, other: &Planet) -> bool {
+ self.pos.y == other.pos.y && self.vel.y == other.vel.y
+ }
+
+ fn equal_z(&self, other: &Planet) -> bool {
+ self.pos.z == other.pos.z && self.vel.z == other.vel.z
+ }
+}
+
+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,
+ }
+ }
+}