diff --git a/day12/Cargo.lock b/day12/Cargo.lock new file mode 100644 index 0000000..f9a061e --- /dev/null +++ b/day12/Cargo.lock @@ -0,0 +1,143 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "aho-corasick" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "autocfg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "day12" +version = "0.1.0" +dependencies = [ + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "memchr" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "num" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-bigint 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", + "num-complex 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "num-iter 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", + "num-rational 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-bigint" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-complex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-integer" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-iter" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-rational" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-bigint 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", + "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-traits" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex-syntax" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "thread_local" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[metadata] +"checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d" +"checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" +"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +"checksum memchr 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3197e20c7edb283f87c071ddfc7a2cca8f8e0b888c242959846a6fce03c72223" +"checksum num 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36" +"checksum num-bigint 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f115de20ad793e857f76da2563ff4a09fbcfd6fe93cca0c5d996ab5f3ee38d" +"checksum num-complex 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95" +"checksum num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" +"checksum num-iter 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "dfb0800a0291891dd9f4fe7bd9c19384f98f7fbe0cd0f39a2c6b88b9868bbc00" +"checksum num-rational 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "da4dc79f9e6c81bef96148c8f6b8e72ad4541caa4a24373e900a36da07de03a3" +"checksum num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" +"checksum regex 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b5508c1941e4e7cb19965abef075d35a9a8b5cdf0846f30b4050e9b55dc55e87" +"checksum regex-syntax 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "e734e891f5b408a29efbf8309e656876276f49ab6a6ac208600b4419bd893d90" +"checksum thread_local 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "88ddf1ad580c7e3d1efff877d972bcc93f995556b9087a5a259630985c88ceab" diff --git a/day12/Cargo.toml b/day12/Cargo.toml new file mode 100644 index 0000000..6772fd6 --- /dev/null +++ b/day12/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "day12" +version = "0.1.0" +authors = ["Tyler Hallada "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +lazy_static = "1.4.0" +num = "0.2.1" +regex = "1.3.3" diff --git a/day12/input/input.txt b/day12/input/input.txt new file mode 100644 index 0000000..c6134ab --- /dev/null +++ b/day12/input/input.txt @@ -0,0 +1,4 @@ + + + + diff --git a/day12/input/test1.txt b/day12/input/test1.txt new file mode 100644 index 0000000..89cc805 --- /dev/null +++ b/day12/input/test1.txt @@ -0,0 +1,4 @@ + + + + diff --git a/day12/input/test2.txt b/day12/input/test2.txt new file mode 100644 index 0000000..1078293 --- /dev/null +++ b/day12/input/test2.txt @@ -0,0 +1,4 @@ + + + + diff --git a/day12/src/main.rs b/day12/src/main.rs new file mode 100644 index 0000000..68eec99 --- /dev/null +++ b/day12/src/main.rs @@ -0,0 +1,418 @@ +#[macro_use] +extern crate lazy_static; + +use std::collections::HashSet; +use std::error::Error; +use std::fs::File; +use std::io::{prelude::*, BufReader}; +use std::ops::AddAssign; +use std::ops::Index; +use std::result; +use std::str::FromStr; + +use num::integer::lcm; +use regex::Regex; + +type Result = result::Result>; + +const INPUT: &str = "input/input.txt"; + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] +struct Vector { + x: i64, + y: i64, + z: i64, +} + +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +struct Body { + position: Vector, + velocity: Vector, +} + +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +struct NBody { + bodies: Vec, +} + +impl FromStr for Body { + type Err = Box; + + fn from_str(s: &str) -> Result { + lazy_static! { + static ref RE: Regex = + Regex::new(r"-?\d+), y=(?P-?\d+), z=(?P-?\d+)>").unwrap(); + } + + let captures = match RE.captures(s) { + None => { + return Err(From::from("Malformed scan, no positions could be found")); + } + Some(captures) => captures, + }; + + Ok(Body { + position: Vector { + x: captures["x"].parse()?, + y: captures["y"].parse()?, + z: captures["z"].parse()?, + }, + velocity: Vector::new(), + }) + } +} + +impl Vector { + fn new() -> Vector { + Vector { x: 0, y: 0, z: 0 } + } +} + +impl AddAssign for Vector { + fn add_assign(&mut self, other: Self) { + *self = Self { + x: self.x + other.x, + y: self.y + other.y, + z: self.z + other.z, + } + } +} + +impl Index<&str> for Vector { + type Output = i64; + + fn index(&self, index: &str) -> &i64 { + match index { + "x" => &self.x, + "y" => &self.y, + "z" => &self.z, + _ => panic!("unknown field: {}", index), + } + } +} + +impl Body { + fn add_gravity(&self, gravity: &mut Vector, other: &Self) { + if self.position.x > other.position.x { + gravity.x -= 1; + } else if self.position.x < other.position.x { + gravity.x += 1; + } + + if self.position.y > other.position.y { + gravity.y -= 1; + } else if self.position.y < other.position.y { + gravity.y += 1; + } + + if self.position.z > other.position.z { + gravity.z -= 1; + } else if self.position.z < other.position.z { + gravity.z += 1; + } + } +} + +impl NBody { + fn run_step(&mut self) { + let mut gravities = Vec::new(); + for body in self.bodies.iter() { + let mut gravity = Vector::new(); + for other_body in self.bodies.iter() { + body.add_gravity(&mut gravity, other_body); + } + gravities.push(gravity); + } + + for (index, gravity) in gravities.into_iter().enumerate() { + self.bodies[index].velocity += gravity; + let velocity = self.bodies[index].velocity; + self.bodies[index].position += velocity; + } + } + + fn total_energy(&self) -> i64 { + let mut total_energy = 0; + for body in self.bodies.iter() { + let potential_energy = + body.position.x.abs() + body.position.y.abs() + body.position.z.abs(); + let kinetic_energy = + body.velocity.x.abs() + body.velocity.y.abs() + body.velocity.z.abs(); + total_energy += potential_energy * kinetic_energy; + } + total_energy + } + + fn state(&self, component: &str) -> [(i64, i64); 4] { + [ + ( + self.bodies[0].position[component], + self.bodies[0].velocity[component], + ), + ( + self.bodies[1].position[component], + self.bodies[1].velocity[component], + ), + ( + self.bodies[2].position[component], + self.bodies[2].velocity[component], + ), + ( + self.bodies[3].position[component], + self.bodies[3].velocity[component], + ), + ] + } +} + +fn read_moon_scan(filename: &str) -> Result { + let file = File::open(filename)?; + let reader = BufReader::new(file); + let mut moons = vec![]; + + for line in reader.lines() { + moons.push(line?.parse()?); + } + + Ok(NBody { bodies: moons }) +} + +fn solve_part1(filename: &str) -> Result { + let mut nbody = read_moon_scan(filename)?; + for _ in 0..1000 { + nbody.run_step(); + } + Ok(nbody.total_energy()) +} + +fn solve_part2(filename: &str) -> Result { + let mut step_count = 0; + let mut x_states: HashSet<[(i64, i64); 4]> = HashSet::new(); + let mut y_states: HashSet<[(i64, i64); 4]> = HashSet::new(); + let mut z_states: HashSet<[(i64, i64); 4]> = HashSet::new(); + let mut x_repeated_step_count = None; + let mut y_repeated_step_count = None; + let mut z_repeated_step_count = None; + let mut nbody = read_moon_scan(filename)?; + while x_repeated_step_count == None + || y_repeated_step_count == None + || z_repeated_step_count == None + { + if x_repeated_step_count == None { + let x_state = nbody.state("x"); + if x_states.contains(&x_state) { + x_repeated_step_count = Some(step_count); + } else { + x_states.insert(x_state); + } + } + + if y_repeated_step_count == None { + let y_state = nbody.state("y"); + if y_states.contains(&y_state) { + y_repeated_step_count = Some(step_count); + } else { + y_states.insert(y_state); + } + } + + if z_repeated_step_count == None { + let z_state = nbody.state("z"); + if z_states.contains(&z_state) { + z_repeated_step_count = Some(step_count); + } else { + z_states.insert(z_state); + } + } + + nbody.run_step(); + step_count += 1; + } + + Ok(lcm( + x_repeated_step_count.unwrap(), + lcm( + y_repeated_step_count.unwrap(), + z_repeated_step_count.unwrap(), + ), + )) +} + +fn main() -> Result<()> { + println!("Part 1: {}", solve_part1(INPUT)?); + println!("Part 2: {}", solve_part2(INPUT)?); + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + const TEST_INPUT1: &str = "input/test1.txt"; + const TEST_INPUT2: &str = "input/test2.txt"; + fn nbody_1() -> NBody { + NBody { + bodies: vec![ + Body { + position: Vector { x: -1, y: 0, z: 2 }, + velocity: Vector { x: 0, y: 0, z: 0 }, + }, + Body { + position: Vector { + x: 2, + y: -10, + z: -7, + }, + velocity: Vector { x: 0, y: 0, z: 0 }, + }, + Body { + position: Vector { x: 4, y: -8, z: 8 }, + velocity: Vector { x: 0, y: 0, z: 0 }, + }, + Body { + position: Vector { x: 3, y: 5, z: -1 }, + velocity: Vector { x: 0, y: 0, z: 0 }, + }, + ], + } + } + fn nbody_1_after_10_steps() -> NBody { + NBody { + bodies: vec![ + Body { + position: Vector { x: 2, y: 1, z: -3 }, + velocity: Vector { x: -3, y: -2, z: 1 }, + }, + Body { + position: Vector { x: 1, y: -8, z: 0 }, + velocity: Vector { x: -1, y: 1, z: 3 }, + }, + Body { + position: Vector { x: 3, y: -6, z: 1 }, + velocity: Vector { x: 3, y: 2, z: -3 }, + }, + Body { + position: Vector { x: 2, y: 0, z: 4 }, + velocity: Vector { x: 1, y: -1, z: -1 }, + }, + ], + } + } + fn nbody_2() -> NBody { + NBody { + bodies: vec![ + Body { + position: Vector { + x: -8, + y: -10, + z: 0, + }, + velocity: Vector { x: 0, y: 0, z: 0 }, + }, + Body { + position: Vector { x: 5, y: 5, z: 10 }, + velocity: Vector { x: 0, y: 0, z: 0 }, + }, + Body { + position: Vector { x: 2, y: -7, z: 3 }, + velocity: Vector { x: 0, y: 0, z: 0 }, + }, + Body { + position: Vector { x: 9, y: -8, z: -3 }, + velocity: Vector { x: 0, y: 0, z: 0 }, + }, + ], + } + } + fn nbody_2_after_100_steps() -> NBody { + NBody { + bodies: vec![ + Body { + position: Vector { + x: 8, + y: -12, + z: -9, + }, + velocity: Vector { x: -7, y: 3, z: 0 }, + }, + Body { + position: Vector { + x: 13, + y: 16, + z: -3, + }, + velocity: Vector { + x: 3, + y: -11, + z: -5, + }, + }, + Body { + position: Vector { + x: -29, + y: -11, + z: -1, + }, + velocity: Vector { x: -3, y: 7, z: 4 }, + }, + Body { + position: Vector { + x: 16, + y: -13, + z: 23, + }, + velocity: Vector { x: 7, y: 1, z: 1 }, + }, + ], + } + } + + #[test] + fn reads_moon_scan_file() { + assert_eq!(read_moon_scan(TEST_INPUT1).unwrap(), nbody_1()); + assert_eq!(read_moon_scan(TEST_INPUT2).unwrap(), nbody_2()); + } + + #[test] + fn runs_10_steps() { + let mut nbody = read_moon_scan(TEST_INPUT1).unwrap(); + for _ in 0..10 { + nbody.run_step(); + } + assert_eq!(nbody, nbody_1_after_10_steps()); + } + + #[test] + fn runs_100_steps() { + let mut nbody = read_moon_scan(TEST_INPUT2).unwrap(); + for _ in 0..100 { + nbody.run_step(); + } + assert_eq!(nbody, nbody_2_after_100_steps()); + } + + #[test] + fn calculates_total_energy_after_10_steps() { + let mut nbody = read_moon_scan(TEST_INPUT1).unwrap(); + for _ in 0..10 { + nbody.run_step(); + } + assert_eq!(nbody.total_energy(), 179); + } + + #[test] + fn calculates_total_energy_after_100_steps() { + let mut nbody = read_moon_scan(TEST_INPUT2).unwrap(); + for _ in 0..100 { + nbody.run_step(); + } + assert_eq!(nbody.total_energy(), 1940); + } + + #[test] + fn finds_repeated_states() { + assert_eq!(solve_part2(TEST_INPUT1).unwrap(), 2772); + assert_eq!(solve_part2(TEST_INPUT2).unwrap(), 4686774924); + } +}