year2024/
day14.rs

1use utils::number::chinese_remainder;
2use utils::point::Point2D;
3use utils::prelude::*;
4
5/// Finding when robots arrange themselves into a picture.
6#[derive(Clone, Debug)]
7pub struct Day14 {
8    robots: Vec<Robot>,
9}
10
11#[derive(Copy, Clone, Debug)]
12struct Robot {
13    position: Point2D<i32>,
14    velocity: Point2D<i32>,
15}
16
17const WIDTH: i32 = 101;
18const HEIGHT: i32 = 103;
19
20impl Day14 {
21    pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
22        Ok(Self {
23            robots: parser::number_range(0..=WIDTH - 1)
24                .with_prefix("p=")
25                .then(parser::number_range(0..=HEIGHT - 1).with_prefix(","))
26                .then(parser::i32().with_prefix(" v="))
27                .then(parser::i32().with_prefix(","))
28                .map(|(px, py, vx, vy)| Robot {
29                    position: Point2D::new(px, py),
30                    velocity: Point2D::new(vx, vy),
31                })
32                .parse_lines(input)?,
33        })
34    }
35
36    #[must_use]
37    pub fn part1(&self) -> u64 {
38        let mut counts = [0; 4];
39        for &(mut r) in self.robots.iter() {
40            r.position += r.velocity * 100;
41            r.position.x = r.position.x.rem_euclid(WIDTH);
42            r.position.y = r.position.y.rem_euclid(HEIGHT);
43
44            if r.position.x == WIDTH / 2 || r.position.y == HEIGHT / 2 {
45                continue;
46            }
47
48            let mut quadrant = 0;
49            if r.position.x > WIDTH / 2 {
50                quadrant += 2;
51            }
52            if r.position.y > HEIGHT / 2 {
53                quadrant += 1;
54            }
55            counts[quadrant] += 1;
56        }
57        counts.iter().product()
58    }
59
60    #[must_use]
61    pub fn part2(&self) -> u32 {
62        // The only time there are more than 30 robots in a single row and more than 30 robots in a
63        // single column is when the robots are arranged into a Christmas Tree. Additionally, each
64        // robot's x position repeats every 101 seconds, and y position repeats every 103 seconds.
65        // This allows separately finding the time mod 101 where there are enough robots in a single
66        // column, and then finding the time mod 103 where there are enough robots in a single row.
67        // This then gives us two equations which can be solved using the Chinese reminder theorem.
68        //      result % 101 == A
69        //      result % 103 == B
70
71        let a = (0..WIDTH)
72            .find(|&t| {
73                let mut columns = [0u8; WIDTH as usize];
74                for r in &self.robots {
75                    let col = (r.position.x + r.velocity.x * t).rem_euclid(WIDTH);
76                    columns[col as usize] += 1;
77                }
78                columns.iter().any(|&b| b >= 30)
79            })
80            .expect("expected a time to have more than 30 robots in a column");
81
82        let b = (0..HEIGHT)
83            .find(|&t| {
84                let mut rows = [0u8; HEIGHT as usize];
85                for r in &self.robots {
86                    let row = (r.position.y + r.velocity.y * t).rem_euclid(HEIGHT);
87                    rows[row as usize] += 1;
88                }
89                rows.iter().any(|&b| b >= 30)
90            })
91            .expect("expected a time to have more than 30 robots in a row");
92
93        chinese_remainder([a, b], [WIDTH, HEIGHT]).unwrap() as u32
94    }
95}
96
97examples!(Day14 -> (u32, u32) []);