Skip to content

Commit 52ce337

Browse files
committed
AOC 2024 Day 14
1 parent bac1567 commit 52ce337

File tree

2 files changed

+209
-0
lines changed

2 files changed

+209
-0
lines changed

aoc/2024/rust/data/examples/14.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
p=0,4 v=3,-3
2+
p=6,3 v=-1,-3
3+
p=10,3 v=-1,2
4+
p=2,0 v=2,-1
5+
p=0,0 v=1,3
6+
p=3,0 v=-2,-2
7+
p=7,6 v=-1,-3
8+
p=3,0 v=-1,-2
9+
p=9,3 v=2,3
10+
p=7,3 v=-1,2
11+
p=2,4 v=2,-3
12+
p=9,5 v=-3,-3

aoc/2024/rust/src/bin/14.rs

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
advent_of_code::solution!(14);
2+
3+
use glam::{u32, IVec2};
4+
use nom::{
5+
bytes::complete::tag,
6+
character::complete::{char, i32, newline, space1},
7+
combinator::map,
8+
multi::separated_list1,
9+
sequence::{preceded, separated_pair},
10+
IResult,
11+
};
12+
13+
#[derive(Debug, PartialEq, Clone, Copy)]
14+
struct Robot {
15+
position: IVec2,
16+
velocity: IVec2,
17+
}
18+
19+
impl Robot {
20+
fn get_position_after_t_seconds(&self, t: i32, grid_size: IVec2) -> IVec2 {
21+
// let x = circular_index(num_cols, self.position.x + self.velocity.x * t);
22+
// let y = circular_index(num_rows, self.position.y + self.velocity.y * t);
23+
// IVec2::new(
24+
// (self.position.x + self.velocity.x * t).rem_euclid(num_cols),
25+
// (self.position.y + self.velocity.y * t).rem_euclid(num_rows),
26+
// )
27+
(self.position + self.velocity * t).rem_euclid(grid_size)
28+
}
29+
}
30+
31+
#[allow(dead_code)]
32+
fn circular_index(len: i32, index: i32) -> i32 {
33+
((index % len) + len) % len
34+
}
35+
36+
#[derive(Debug)]
37+
enum Quadrant {
38+
NE,
39+
SE,
40+
SW,
41+
NW,
42+
}
43+
44+
fn get_cuadrant(position: IVec2, grid_size: IVec2) -> Option<Quadrant> {
45+
let (x, y) = (grid_size.x / 2, grid_size.y / 2);
46+
match (position.x.cmp(&x), position.y.cmp(&y)) {
47+
(std::cmp::Ordering::Less, std::cmp::Ordering::Less) => Some(Quadrant::NW),
48+
(std::cmp::Ordering::Less, std::cmp::Ordering::Greater) => Some(Quadrant::SW),
49+
(std::cmp::Ordering::Greater, std::cmp::Ordering::Less) => Some(Quadrant::NE),
50+
(std::cmp::Ordering::Greater, std::cmp::Ordering::Greater) => Some(Quadrant::SE),
51+
_ => None,
52+
}
53+
}
54+
55+
fn parse_vector(input: &str) -> IResult<&str, IVec2> {
56+
map(separated_pair(i32, char(','), i32), |(x, y)| {
57+
IVec2::new(x, y)
58+
})(input)
59+
}
60+
61+
fn parse_robot(input: &str) -> IResult<&str, Robot> {
62+
separated_pair(
63+
preceded(tag("p="), parse_vector),
64+
space1,
65+
preceded(tag("v="), parse_vector),
66+
)(input)
67+
.map(|(rem, (position, velocity))| (rem, Robot { position, velocity }))
68+
}
69+
70+
fn parse(input: &str) -> IResult<&str, Vec<Robot>> {
71+
separated_list1(newline, parse_robot)(input)
72+
}
73+
74+
const GRID_SIZE: IVec2 = if cfg!(test) {
75+
IVec2::new(11, 7)
76+
} else {
77+
IVec2::new(101, 103)
78+
};
79+
80+
pub fn part_one(input: &str) -> Option<u32> {
81+
let (_, robots) = parse(input).unwrap();
82+
let robots_per_quadrant = robots
83+
.iter()
84+
.map(|r| r.get_position_after_t_seconds(100, GRID_SIZE))
85+
.fold([0; 4], |mut acc, position| {
86+
match get_cuadrant(position, GRID_SIZE) {
87+
Some(Quadrant::NE) => {
88+
acc[0] += 1;
89+
acc
90+
}
91+
Some(Quadrant::SE) => {
92+
acc[1] += 1;
93+
acc
94+
}
95+
Some(Quadrant::SW) => {
96+
acc[2] += 1;
97+
acc
98+
}
99+
Some(Quadrant::NW) => {
100+
acc[3] += 1;
101+
acc
102+
}
103+
None => acc,
104+
}
105+
});
106+
Some(robots_per_quadrant.into_iter().product::<u32>())
107+
}
108+
109+
// 7623
110+
pub fn part_two(input: &str) -> Option<u32> {
111+
let (_, robots) = parse(input).unwrap();
112+
let mut positions = [false; 10650];
113+
'times: for t in 1..10000 {
114+
positions.fill(false);
115+
// positions.clear();
116+
// if robots
117+
// .iter()
118+
// .map(|r| (r.position + r.velocity * t).rem_euclid(GRID_SIZE))
119+
// .unique()
120+
// .count()
121+
// == robots.len()
122+
// {
123+
// return Some(t as _);
124+
// }
125+
126+
// Iterator + unique 71ms
127+
// for loop + hashset 32ms
128+
// for loop + array 2.7ms
129+
for pos in robots
130+
.iter()
131+
.map(|r| r.get_position_after_t_seconds(t, GRID_SIZE))
132+
{
133+
if positions[(pos.y * GRID_SIZE.y + pos.x) as usize] {
134+
continue 'times;
135+
}
136+
positions[(pos.y * GRID_SIZE.y + pos.x) as usize] = true;
137+
}
138+
return Some(t as _);
139+
}
140+
None
141+
}
142+
143+
#[allow(dead_code)]
144+
fn debug_grid(robots: Vec<Robot>, num_cols: i32, num_rows: i32) {
145+
dbg!(num_cols, num_rows);
146+
let mut grid = vec![vec!['.'; num_cols as usize]; num_rows as usize];
147+
dbg!(grid.len(), grid[0].len());
148+
for r in robots.into_iter() {
149+
grid[r.position.y as usize][r.position.x as usize] = '#';
150+
}
151+
grid.iter().for_each(|r| {
152+
r.iter().for_each(|c| print!("{c}"));
153+
println!();
154+
});
155+
}
156+
157+
#[cfg(test)]
158+
mod tests {
159+
use super::*;
160+
use test_case::test_case;
161+
162+
#[test]
163+
fn test_part_one() {
164+
let result = part_one(&advent_of_code::template::read_file("examples", DAY));
165+
assert_eq!(result, Some(12));
166+
}
167+
168+
#[test]
169+
fn test_part_two() {
170+
let result = part_two(&advent_of_code::template::read_file("examples", DAY));
171+
assert_eq!(result, None);
172+
}
173+
174+
#[test_case("p=9,3 v=2,3",Robot {
175+
position: IVec2::new(9, 3),
176+
velocity: IVec2::new(2, 3)
177+
}; "Robot 1" )]
178+
#[test_case("p=7,6 v=-1,-3",Robot {
179+
position: IVec2::new(7, 6),
180+
velocity: IVec2::new(-1, -3)
181+
}; "Robot 2" )]
182+
fn test_parse_robot(input: &str, expected: Robot) {
183+
let (_, result) = parse_robot(input).unwrap();
184+
assert_eq!(result, expected);
185+
}
186+
187+
#[test_case("p=2,4 v=2,-3", 0, IVec2::new(2, 4); "t 0" )]
188+
#[test_case("p=2,4 v=2,-3", 1, IVec2::new(4, 1); "t 1" )]
189+
#[test_case("p=2,4 v=2,-3", 2, IVec2::new(6, 5); "t 2" )]
190+
#[test_case("p=2,4 v=2,-3", 3, IVec2::new(8, 2); "t 3" )]
191+
#[test_case("p=2,4 v=2,-3", 4, IVec2::new(10, 6); "t 4" )]
192+
#[test_case("p=2,4 v=2,-3", 5, IVec2::new(1, 3); "t 5" )]
193+
fn test_position_after_t(robot_str: &str, t: i32, expected: IVec2) {
194+
let (_, robot) = parse_robot(robot_str).unwrap();
195+
assert_eq!(robot.get_position_after_t_seconds(t, GRID_SIZE), expected);
196+
}
197+
}

0 commit comments

Comments
 (0)