Skip to main content

advent_of_code/year2019/
day12.rs

1use crate::input::Input;
2
3#[derive(Clone)]
4struct Moons {
5    positions: [[i32; 3]; 4],
6    velocities: [[i32; 3]; 4],
7}
8
9pub const fn gcd(mut a: u64, mut b: u64) -> u64 {
10    while b != 0 {
11        let tmp = a;
12        a = b;
13        b = tmp % b;
14    }
15    a
16}
17
18pub const fn lcd(a: u64, b: u64) -> u64 {
19    a * b / gcd(a, b)
20}
21
22pub const fn lcd3(a: u64, b: u64, c: u64) -> u64 {
23    lcd(a, lcd(b, c))
24}
25
26impl Moons {
27    fn parse(input: &str) -> Result<Self, String> {
28        let mut positions = [[0; 3]; 4];
29        for (i, line) in input.lines().enumerate() {
30            let error_message = |_| format!("Invalid line: {}", i + 1);
31            let parts: Vec<&str> = line.split(['=', ' ', '>', ',']).collect();
32
33            if parts.len() != 9 {
34                return Err(format!("Invalid line: {}", i + 1));
35            }
36
37            positions[i][0] = parts[1].trim().parse::<i32>().map_err(error_message)?;
38            positions[i][1] = parts[4].trim().parse::<i32>().map_err(error_message)?;
39            positions[i][2] = parts[7].trim().parse::<i32>().map_err(error_message)?;
40        }
41
42        Ok(Self {
43            positions,
44            velocities: [[0; 3]; 4],
45        })
46    }
47
48    fn total_energy(&self) -> u64 {
49        let mut total_energy = 0_u64;
50        for i in 0..4 {
51            let potential_energy = self.positions[i].iter().map(|&x| x.abs()).sum::<i32>() as u64;
52            let kinetic_energy = self.velocities[i].iter().map(|&x| x.abs()).sum::<i32>() as u64;
53            total_energy += potential_energy * kinetic_energy;
54        }
55        total_energy
56    }
57
58    fn step(&mut self) {
59        for i in 0..4 {
60            for j in 0..4 {
61                if i != j {
62                    for k in 0..3 {
63                        self.velocities[i][k] +=
64                            signum(self.positions[j][k] - self.positions[i][k]);
65                    }
66                }
67            }
68        }
69
70        for (i, position) in self.positions.iter_mut().enumerate() {
71            for (k, p) in position.iter_mut().enumerate() {
72                *p += self.velocities[i][k];
73            }
74        }
75    }
76}
77
78const fn signum(value: i32) -> i32 {
79    match value {
80        _ if value < 0 => -1,
81        _ if value == 0 => 0,
82        _ => 1,
83    }
84}
85
86pub fn part1_nth(input_string: &str, n: usize) -> Result<u64, String> {
87    let mut moons = Moons::parse(input_string)?;
88    for _ in 0..n {
89        moons.step();
90    }
91    Ok(moons.total_energy())
92}
93
94pub fn solve(input: &Input) -> Result<u64, String> {
95    if input.is_part_one() {
96        part1_nth(input.text, 1000)
97    } else {
98        let mut moons = Moons::parse(input.text)?;
99        let initial_moons = moons.clone();
100        let mut cycles: [Option<u64>; 3] = [None; 3];
101
102        let mut step = 0;
103        while cycles.iter().any(Option::is_none) {
104            moons.step();
105            step += 1;
106
107            for (i, cycle) in cycles.iter_mut().enumerate() {
108                if cycle.is_none() {
109                    let mut same = true;
110                    for moon in 0..4 {
111                        if initial_moons.positions[moon][i] != moons.positions[moon][i]
112                            || initial_moons.velocities[moon][i] != moons.velocities[moon][i]
113                        {
114                            same = false;
115                        }
116                    }
117                    if same {
118                        cycle.replace(step);
119                    }
120                }
121            }
122        }
123
124        Ok(lcd3(
125            cycles[0].ok_or("Cycles not found")?,
126            cycles[1].ok_or("Cycles not found")?,
127            cycles[2].ok_or("Cycles not found")?,
128        ))
129    }
130}
131
132#[test]
133pub fn tests() {
134    assert_eq!(
135        part1_nth(
136            "<x=-1, y=0, z=2>
137<x=2, y=-10, z=-7>
138<x=4, y=-8, z=8>
139<x=3, y=5, z=-1>",
140            10
141        ),
142        Ok(179)
143    );
144
145    let input = include_str!("day12_input.txt");
146    test_part_one!(input => 6220);
147    test_part_two!(input => 548_525_804_273_976);
148}