aoc/year2024/day14.rs
1//! # Restroom Redoubt
2//!
3//! For part one we jump straight to the final position by multiplying the velocity by 100.
4//! The image appears in part two when the positions of all robots are unique.
5//!
6//! The x coordinates repeat every 101 seconds and the y coordinates repeat every 103 seconds.
7//! For part two, we can treat the coordinates independently, searching for a time when the
8//! [variance](https://en.wikipedia.org/wiki/Variance) of the robots' coordinates is at a minimum.
9//!
10//! Using the [Chinese Remainder Theorem](https://en.wikipedia.org/wiki/Chinese_remainder_theorem)
11//! we combine the two times into a single time mod 10,403 that is the answer.
12use std::cmp::Ordering::*;
13
14use crate::util::iter::*;
15use crate::util::parse::*;
16
17const WIDTH: i32 = 101;
18const HEIGHT: i32 = 103;
19
20type Axis = Vec<(i32, i32)>;
21type Input = (Axis, Axis);
22
23pub fn parse(input: &str) -> Input {
24 input
25 .iter_signed::<i32>()
26 .chunk::<4>()
27 .map(|[x, y, dx, dy]| ((x, dx.rem_euclid(WIDTH)), (y, dy.rem_euclid(HEIGHT))))
28 .unzip()
29}
30
31pub fn part1(input: &Input) -> i32 {
32 let (xs, ys) = input;
33 let mut quadrants = [0; 4];
34
35 for (&(x, dx), &(y, dy)) in xs.iter().zip(ys) {
36 let x = (x + 100 * dx) % WIDTH;
37 let y = (y + 100 * dy) % HEIGHT;
38
39 match (x.cmp(&50), y.cmp(&51)) {
40 (Less, Less) => quadrants[0] += 1,
41 (Less, Greater) => quadrants[1] += 1,
42 (Greater, Less) => quadrants[2] += 1,
43 (Greater, Greater) => quadrants[3] += 1,
44 _ => (),
45 }
46 }
47
48 quadrants.iter().product()
49}
50
51pub fn part2(input: &Input) -> i32 {
52 let (xs, ys) = input;
53
54 // Search for times modulo WIDTH when the tree could possibly exist using x coordinates only,
55 // and times modulo HEIGHT when the tree could possibly exist using y coordinates only.
56 let t = minimum_variance::<WIDTH>(xs);
57 let u = minimum_variance::<HEIGHT>(ys);
58
59 // Combine indices using the Chinese Remainder Theorem to get index mod 10,403.
60 (5_253 * t + 5_151 * u) % (WIDTH * HEIGHT)
61}
62
63/// Calculate the [population variance](https://en.wikipedia.org/wiki/Variance#Population_variance)
64/// for each time in the period, returning the time when at the minimum variance.
65///
66/// Const generics allow the compiler to optimize the modulus calculation, making it 3 times faster.
67fn minimum_variance<const PERIOD: i32>(axis: &Axis) -> i32 {
68 let n = axis.len() as i32;
69 (0..PERIOD)
70 .min_by_key(|time| {
71 let mut sum = 0;
72 let mut sum2 = 0;
73
74 for &(position, velocity) in axis {
75 let robot = (position + velocity * time) % PERIOD;
76 sum += robot;
77 sum2 += robot * robot;
78 }
79
80 // Variance scaled by n² in order to keep values integer.
81 n * sum2 - sum * sum
82 })
83 .unwrap()
84}