Skip to content

Commit eaa553e

Browse files
committed
Faster approach seaching for the bounding box
1 parent e808d24 commit eaa553e

File tree

2 files changed

+66
-46
lines changed

2 files changed

+66
-46
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ Performance is reasonable even on older hardware, for example a 2011 MacBook Pro
8585
| 11 | [Plutonian Pebbles](https://adventofcode.com/2024/day/11) | [Source](src/year2024/day11.rs) | 248 |
8686
| 12 | [Garden Groups](https://adventofcode.com/2024/day/12) | [Source](src/year2024/day12.rs) | 289 |
8787
| 13 | [Claw Contraption](https://adventofcode.com/2024/day/13) | [Source](src/year2024/day13.rs) | 14 |
88-
| 14 | [Restroom Redoubt](https://adventofcode.com/2024/day/14) | [Source](src/year2024/day14.rs) | 456 |
88+
| 14 | [Restroom Redoubt](https://adventofcode.com/2024/day/14) | [Source](src/year2024/day14.rs) | 74 |
8989

9090
## 2023
9191

src/year2024/day14.rs

Lines changed: 65 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,27 @@
44
//! The image appears in part two when the positions of all robots are unique.
55
//!
66
//! The x coordinates repeat every 101 seconds and the y coordinates repeat every 103 seconds.
7-
//! Calculating each axis independently then looking it up is twice as fast
8-
//! as calculating as needed.
7+
//! First we check for times when the robot x coordinates could form the left and right columns
8+
//! of the tree's bounding box. This gives a time `t` mod 101.
9+
//!
10+
//! Then we check the y coordinates looking for the top and bottom rows of the bounding box,
11+
//! giving a time `u` mod 103.
12+
//!
13+
//! Using the [Chinese Remainder Theorem](https://en.wikipedia.org/wiki/Chinese_remainder_theorem)
14+
//! we combine the two times into a single time mod 10403 that is the answer.
915
use crate::util::iter::*;
10-
use crate::util::math::*;
1116
use crate::util::parse::*;
1217

13-
type Robot = [i32; 4];
18+
type Robot = [usize; 4];
1419

1520
pub fn parse(input: &str) -> Vec<Robot> {
16-
input.iter_signed().chunk::<4>().collect()
21+
input
22+
.iter_signed::<i32>()
23+
.chunk::<4>()
24+
.map(|[x, y, dx, dy]| {
25+
[x as usize, y as usize, dx.rem_euclid(101) as usize, dy.rem_euclid(103) as usize]
26+
})
27+
.collect()
1728
}
1829

1930
pub fn part1(input: &[Robot]) -> i32 {
@@ -23,8 +34,8 @@ pub fn part1(input: &[Robot]) -> i32 {
2334
let mut q4 = 0;
2435

2536
for [x, y, dx, dy] in input {
26-
let x = (x + 100 * dx).rem_euclid(101);
27-
let y = (y + 100 * dy).rem_euclid(103);
37+
let x = (x + 100 * dx) % 101;
38+
let y = (y + 100 * dy) % 103;
2839

2940
if x < 50 {
3041
if y < 51 {
@@ -47,52 +58,61 @@ pub fn part1(input: &[Robot]) -> i32 {
4758
q1 * q2 * q3 * q4
4859
}
4960

50-
pub fn part2(input: &[Robot]) -> usize {
51-
let robots: Vec<_> = input
52-
.iter()
53-
.map(|&[x, y, h, v]| [x, y, h.rem_euclid(101), v.rem_euclid(103)])
54-
.collect();
61+
pub fn part2(robots: &[Robot]) -> usize {
62+
// Search for times mod 101 when the tree could possibly exist using x coordinates only.
63+
// and times mod 103 when the tree could possibly exist using y coordinates only.
64+
let mut rows = Vec::new();
65+
let mut columns = Vec::new();
5566

56-
let coefficient1 = 103 * 103.mod_inv(101).unwrap();
57-
let coefficient2 = 101 * 101.mod_inv(103).unwrap();
58-
let horizontal: Vec<_> = (0..101).map(|n| n.mod_inv(101)).collect();
59-
let vertical: Vec<_> = (0..103).map(|n| n.mod_inv(103)).collect();
67+
for time in 0..103 {
68+
let mut xs = [0; 101];
69+
let mut ys = [0; 103];
6070

61-
let mut unique = vec![true; 10403];
71+
for [x, y, dx, dy] in robots {
72+
let x = (x + time * dx) % 101;
73+
xs[x] += 1;
74+
let y = (y + time * dy) % 103;
75+
ys[y] += 1;
76+
}
6277

63-
for (i, &[x1, y1, h1, v1]) in robots.iter().enumerate().skip(1) {
64-
for &[x2, y2, h2, v2] in robots.iter().take(i) {
65-
if x1 == x2 && h1 == h2 {
66-
if let Some(b) = vertical[to_index(v2 - v1, 103)] {
67-
let u = to_index((y1 - y2) * b, 103);
78+
// Tree bounding box is 31x33.
79+
if time < 101 && xs.iter().filter(|&&c| c >= 33).count() >= 2 {
80+
columns.push(time);
81+
}
82+
if ys.iter().filter(|&&c| c >= 31).count() >= 2 {
83+
rows.push(time);
84+
}
85+
}
6886

69-
for n in (0..10403).step_by(103) {
70-
unique[n + u] = false;
71-
}
72-
}
73-
} else if y1 == y2 && v1 == v2 {
74-
if let Some(a) = horizontal[to_index(h2 - h1, 101)] {
75-
let t = to_index((x1 - x2) * a, 101);
87+
// If there's only one combination then return answer.
88+
if rows.len() == 1 && columns.len() == 1 {
89+
let t = columns[0];
90+
let u = rows[0];
91+
// Combine indices using the Chinese Remainder Theorem to get index mod 10403.
92+
return (5253 * t + 5151 * u) % 10403;
93+
}
7694

77-
for n in (0..10403).step_by(101) {
78-
unique[n + t] = false;
79-
}
80-
}
81-
} else if let Some(a) = horizontal[to_index(h2 - h1, 101)] {
82-
if let Some(b) = vertical[to_index(v2 - v1, 103)] {
83-
let t = (x1 - x2) * a;
84-
let u = (y1 - y2) * b;
85-
let crt = to_index(t * coefficient1 + u * coefficient2, 10403);
86-
unique[crt] = false;
95+
// Backup check looking for time when all robot positions are unique.
96+
let mut floor = vec![0; 10403];
97+
98+
for &t in &columns {
99+
'outer: for &u in &rows {
100+
let time = (5253 * t + 5151 * u) % 10403;
101+
102+
for &[x, y, dx, dy] in robots {
103+
let x = (x + time * dx) % 101;
104+
let y = (y + time * dy) % 103;
105+
106+
let index = 101 * y + x;
107+
if floor[index] == time {
108+
continue 'outer;
87109
}
110+
floor[index] = time;
88111
}
112+
113+
return time;
89114
}
90115
}
91116

92-
unique.iter().position(|&u| u).unwrap()
93-
}
94-
95-
#[inline]
96-
fn to_index(a: i32, m: i32) -> usize {
97-
a.rem_euclid(m) as usize
117+
unreachable!()
98118
}

0 commit comments

Comments
 (0)