fn strip_prefix<'a>(prefix: &str, input: &'a str) -> &'a str { assert!(input.starts_with(prefix)); &input[prefix.len()..] } fn strip_suffix<'a>(suffix: &str, input: &'a str) -> &'a str { assert!(input.ends_with(suffix)); &input[..input.len() - suffix.len()] } #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] pub struct Position { pub x: i32, pub y: i32, pub z: i32, } impl Position { pub fn parse(input: &str) -> Self { let input = strip_suffix(">", strip_prefix("<", input.trim())); let mut parts = input.split(","); let x = strip_prefix("x=", parts.next().expect("need x component").trim()).parse().unwrap(); let y = strip_prefix("y=", parts.next().expect("need y component").trim()).parse().unwrap(); let z = strip_prefix("z=", parts.next().expect("need z component").trim()).parse().unwrap(); assert!(parts.next().is_none(), "unexpected data"); Position { x, y, z } } pub fn potential_energy(self) -> u32 { self.x.abs() as u32 + self.y.abs() as u32 + self.z.abs() as u32 } } #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] pub struct Velocity { pub dx: i32, pub dy: i32, pub dz: i32, } impl Velocity { pub fn kinetic_energy(self) -> u32 { self.dx.abs() as u32 + self.dy.abs() as u32 + self.dz.abs() as u32 } } impl std::ops::AddAssign for Position { fn add_assign(&mut self, rhs: Velocity) { self.x += rhs.dx; self.y += rhs.dy; self.z += rhs.dz; } } fn parse_moons(input: &str) -> Vec<(Position, Velocity)> { input.lines().map(Position::parse).map(|p| (p, Velocity::default())).collect() } fn step(moons: &mut [(Position, Velocity)]) { for i in 0..moons.len() - 1 { // change velocity based on "gravity" for j in i + 1..moons.len() { let dx = (moons[j].0.x - moons[i].0.x).signum(); moons[i].1.dx += dx; moons[j].1.dx -= dx; let dy = (moons[j].0.y - moons[i].0.y).signum(); moons[i].1.dy += dy; moons[j].1.dy -= dy; let dz = (moons[j].0.z - moons[i].0.z).signum(); moons[i].1.dz += dz; moons[j].1.dz -= dz; } } for moon in moons { moon.0 += moon.1; } } fn energy(moons: &[(Position, Velocity)]) -> u32 { moons.iter().map(|(p, v)| p.potential_energy() * v.kinetic_energy()).sum() } fn energy_after_steps(mut moons: Vec<(Position, Velocity)>, steps: u32) -> u32 { for _ in 0..steps { step(&mut moons); } energy(&moons) } type ExtractedAxis = Vec<(i32, i32)>; fn extract_x(moons: &[(Position, Velocity)]) -> ExtractedAxis { moons.iter().map(|(p, v)| (p.x, v.dx)).collect() } fn extract_y(moons: &[(Position, Velocity)]) -> ExtractedAxis { moons.iter().map(|(p, v)| (p.y, v.dy)).collect() } fn extract_z(moons: &[(Position, Velocity)]) -> ExtractedAxis { moons.iter().map(|(p, v)| (p.z, v.dz)).collect() } fn repeats_after_steps(mut moons: Vec<(Position, Velocity)>) -> u64 { // as `step` is reversible the first repeated state will always be the initial state // find cycle per axis let mut result_x = None; let initial_x = extract_x(&moons); let mut result_y = None; let initial_y = extract_y(&moons); let mut result_z = None; let initial_z = extract_z(&moons); for current in 1u64.. { step(&mut moons); if result_x.is_none() && initial_x == extract_x(&moons) { result_x = Some(current); } if result_y.is_none() && initial_y == extract_y(&moons) { result_y = Some(current); } if result_z.is_none() && initial_z == extract_z(&moons) { result_z = Some(current); } if let (Some(x), Some(y), Some(z)) = (result_x, result_y, result_z) { return num_integer::lcm(num_integer::lcm(x, y), z); } } unreachable!() } fn main() { let moons = parse_moons(include_str!("input.txt")); let energy = energy_after_steps(moons.clone(), 1000); println!("Energy after 1000 steps: {}", energy); println!("Repeats initial state after {} steps", repeats_after_steps(moons)); } #[cfg(test)] mod day12test { use super::*; #[test] fn examples1() { assert_eq!(179, energy_after_steps(parse_moons( " "), 10)); assert_eq!(1940, energy_after_steps(parse_moons( " "), 100)); } #[test] fn examples2() { assert_eq!(2772, repeats_after_steps(parse_moons( " "))); assert_eq!(4686774924, repeats_after_steps(parse_moons( " "))); } }