Skip to content

Commit 94286d9

Browse files
committed
feat(21/2024): initial implementation, which works for some examples
1 parent ad9e47b commit 94286d9

File tree

14 files changed

+254
-30
lines changed

14 files changed

+254
-30
lines changed

readme.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
| [Day 18: RAM Run](src/solutions/year2024/day18.rs) | ⭐⭐ | 2.487 | 204.885 |
3232
| [Day 19: Linen Layout](src/solutions/year2024/day19.rs) | ⭐⭐ | 2.923 | 22.751 |
3333
| [Day 20: Race Condition](src/solutions/year2024/day20.rs) | ⭐⭐ | 7.355 | 280.627 |
34+
| [Day 21: Keypad Conundrum](src/solutions/year2024/day21.rs) | - | - | - |
3435

3536
# 2023
3637

src/solutions/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ pub fn solution(day: DayNumber, year: Year) -> Box<dyn Solution> {
3333
18 => Box::new(year2024::day18::Day18::default()),
3434
19 => Box::new(year2024::day19::Day19),
3535
20 => Box::new(year2024::day20::Day20),
36+
21 => Box::new(year2024::day21::Day21),
3637
_ => panic!("Day not exist"),
3738
},
3839
Year::Year2023 => match i {

src/solutions/year2023/day14.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ impl Solution for Day14 {
1919
let rounded_rocks = Rocks::from(grid.get_all_positions(&'O'));
2020
let cube_rocks = Rocks::from(grid.get_all_positions(&'#'));
2121

22-
let surface_range = grid.surface_range();
22+
let surface_range = grid.surface();
2323

2424
let tilted = Self::tilt_north(surface_range, rounded_rocks, &cube_rocks);
2525

@@ -32,7 +32,7 @@ impl Solution for Day14 {
3232
let grid: Grid<char> = Grid::from(input);
3333
let mut rounded_rocks = Rocks::from(grid.get_all_positions(&'O'));
3434
let cube_rocks = Rocks::from(grid.get_all_positions(&'#'));
35-
let surface_range = grid.surface_range();
35+
let surface_range = grid.surface();
3636

3737
let mut history: Vec<u64> = Vec::new();
3838
let mut cycle_found = false;
@@ -319,9 +319,9 @@ mod tests {
319319
let rounded_rocks = Rocks::from(grid.get_all_positions(&'O'));
320320
let cube_rocks = Rocks::from(grid.get_all_positions(&'#'));
321321

322-
let after_first_cycle = Day14::cycle(grid.surface_range(), rounded_rocks, &cube_rocks);
322+
let after_first_cycle = Day14::cycle(grid.surface(), rounded_rocks, &cube_rocks);
323323

324-
let mut grid: Grid<char> = Grid::filled(grid.surface_range(), '.');
324+
let mut grid: Grid<char> = Grid::filled(grid.surface(), '.');
325325
grid.modify_many(after_first_cycle.all, 'O');
326326
grid.modify_many(cube_rocks.all, '#');
327327

src/solutions/year2023/day16.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ impl Solution for Day16 {
1818

1919
fn part_two(&self, input: &str) -> String {
2020
let grid: Grid<Tile> = Grid::from(input);
21-
let starting_points: Vec<Vector> = grid.surface_range().vectors_pointing_inwards();
21+
let starting_points: Vec<Vector> = grid.surface().vectors_pointing_inwards();
2222

2323
starting_points
2424
.into_iter()
@@ -31,7 +31,7 @@ impl Solution for Day16 {
3131

3232
impl Day16 {
3333
fn energize(start: Vector, grid: &Grid<Tile>) -> usize {
34-
let surface_range = grid.surface_range();
34+
let surface_range = grid.surface();
3535

3636
let mut beams: VecDeque<Vector> = VecDeque::new();
3737
beams.push_back(start);

src/solutions/year2023/day17.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ impl Day17 {
5858
adjacency: &dyn Fn(Node) -> Vec<Node>,
5959
is_end: &dyn Fn(Node) -> bool,
6060
) -> String {
61-
let start_point = grid.surface_range().top_left_corner();
61+
let start_point = grid.surface().top_left_corner();
6262
let cost = |_, next: Node| *grid.get_for_point(&next.vector.position()).unwrap() as usize;
6363
let dijkstra: Dijkstra<Node> = Dijkstra::new(adjacency, &cost, is_end);
6464

@@ -72,12 +72,12 @@ impl Day17 {
7272

7373
fn filter_out_outside_grid(vec: Vec<Node>, grid: &Grid<u8>) -> Vec<Node> {
7474
vec.into_iter()
75-
.filter(|n| grid.surface_range().contains(n.vector.position()))
75+
.filter(|n| grid.surface().contains(n.vector.position()))
7676
.collect()
7777
}
7878

7979
fn is_end_node(node: Node, grid: &Grid<u8>) -> bool {
80-
node.vector.position() == grid.surface_range().bottom_right_corner()
80+
node.vector.position() == grid.surface().bottom_right_corner()
8181
}
8282
}
8383

src/solutions/year2023/day21.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ impl Day21 {
2020
let grid: Grid<char> = Grid::from(input);
2121

2222
let start = grid.get_first_position(&'S').unwrap();
23-
let surface = grid.surface_range();
23+
let surface = grid.surface();
2424
let rocks = grid.get_all_positions(&'#');
2525

2626
let mut reached: HashSet<Point> = HashSet::from([start]);

src/solutions/year2023/day23.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,15 +91,15 @@ impl Day23 {
9191
}
9292
}
9393

94-
let surface = grid.surface_range();
94+
let surface = grid.surface();
9595
let start = surface.top_left_corner().east();
9696
let end = surface.bottom_right_corner().west();
9797

9898
Self::longest_path(start, end, graph, costs).to_string()
9999
}
100100

101101
fn crossroads(grid: &Grid<char>) -> Vec<Point> {
102-
let surface = grid.surface_range();
102+
let surface = grid.surface();
103103
let start = surface.top_left_corner().east();
104104
let end = surface.bottom_right_corner().west();
105105

src/solutions/year2024/day06.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ impl Solution for Day06 {
1717

1818
let obstructions = grid.get_all_positions(&OBSTRUCTION);
1919
let starting_point = grid.get_first_position(&STARTING_POSITION).unwrap();
20-
let surface = grid.surface_range();
20+
let surface = grid.surface();
2121

2222
let mut guard = Vector::new(starting_point, North);
2323
let mut visited_positions: HashSet<Point> = HashSet::new();
@@ -36,7 +36,7 @@ impl Solution for Day06 {
3636

3737
let obstructions = grid.get_all_positions(&OBSTRUCTION);
3838
let starting_point = grid.get_first_position(&STARTING_POSITION).unwrap();
39-
let surface = grid.surface_range();
39+
let surface = grid.surface();
4040

4141
let mut guard = Vector::new(starting_point, North);
4242
let mut visited_positions: HashSet<Point> = HashSet::new();

src/solutions/year2024/day08.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ impl Day08 {
2323
solve_fn: fn(Point, Point, &SurfaceRange) -> Vec<Point>,
2424
) -> String {
2525
let grid: Grid<char> = Grid::from(input);
26-
let surface_range = grid.surface_range();
26+
let surface_range = grid.surface();
2727

2828
grid.elements_with_points()
2929
.iter()
@@ -120,7 +120,7 @@ mod tests {
120120

121121
let (p1, p2) = elements.get(&'a').unwrap().iter().collect_tuple().unwrap();
122122

123-
let mut result = Day08::antinodes_part_one(*p1, *p2, &grid.surface_range());
123+
let mut result = Day08::antinodes_part_one(*p1, *p2, &grid.surface());
124124
let mut expected = elements.get(&'#').unwrap().to_vec();
125125

126126
result.sort();
@@ -147,9 +147,9 @@ mod tests {
147147

148148
let (p1, p2, p3) = elements.get(&'T').unwrap().iter().collect_tuple().unwrap();
149149

150-
let result1 = Day08::antinodes_part_two(*p1, *p2, &grid.surface_range());
151-
let result2 = Day08::antinodes_part_two(*p1, *p3, &grid.surface_range());
152-
let result3 = Day08::antinodes_part_two(*p2, *p3, &grid.surface_range());
150+
let result1 = Day08::antinodes_part_two(*p1, *p2, &grid.surface());
151+
let result2 = Day08::antinodes_part_two(*p1, *p3, &grid.surface());
152+
let result3 = Day08::antinodes_part_two(*p2, *p3, &grid.surface());
153153

154154
let result = concat(vec![result1, result2, result3])
155155
.iter()

src/solutions/year2024/day21.rs

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
use crate::solutions::year2024::day21::Key::{Activate, Dir};
2+
use crate::solutions::Solution;
3+
use crate::utils::direction::Direction;
4+
use crate::utils::graphs::a_star::AStarBuilder;
5+
use crate::utils::grid::Grid;
6+
use crate::utils::point::Point;
7+
use itertools::Itertools;
8+
use std::collections::HashMap;
9+
use std::fmt::{Display, Formatter};
10+
11+
type Positions = HashMap<u8, Point>;
12+
type Adjacent = HashMap<Point, Vec<Point>>;
13+
14+
const NUM_PAD: &str = r#"789
15+
456
16+
123
17+
.0A"#;
18+
const NUM_PAD_ELEMENTS: [u8; 11] = [
19+
b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'A',
20+
];
21+
const ARROW_PAD: &str = r#".^A
22+
<v>"#;
23+
const ARROW_PAD_ELEMENTS: [u8; 5] = [b'^', b'v', b'<', b'>', b'A'];
24+
25+
pub struct Day21;
26+
27+
impl Solution for Day21 {
28+
fn part_one(&self, input: &str) -> String {
29+
let pads = vec![Pad::numeric(), Pad::arrow(), Pad::arrow()];
30+
31+
input
32+
.lines()
33+
.map(|line| {
34+
let path_len = self.path_len(line, &pads);
35+
let num: usize = line.trim_end_matches('A').parse().unwrap();
36+
37+
// println!("{} * {}", path_len, num);
38+
num * path_len
39+
})
40+
.sum::<usize>()
41+
.to_string()
42+
}
43+
44+
fn part_two(&self, _input: &str) -> String {
45+
String::from('0')
46+
}
47+
}
48+
49+
impl Day21 {
50+
fn path_len(&self, code: &str, pads: &Vec<Pad>) -> usize {
51+
let mut current = code.to_string();
52+
53+
for pad in pads {
54+
let path = self.path_for_str(&current, pad);
55+
56+
current = path.iter().map(|key| key.to_string()).collect::<String>();
57+
58+
// println!("{}", current);
59+
}
60+
61+
current.chars().count()
62+
}
63+
fn path_for_str(&self, code: &str, pad: &Pad) -> Vec<Key> {
64+
let code = "A".to_owned() + code;
65+
let a_position = pad.position(b'A').unwrap();
66+
67+
let neighbours = |p: Point| pad.adjacent(&p);
68+
let distance = |p1: Point, p2: Point| {
69+
p1.manhattan_distance(&p2) as usize + p2.manhattan_distance(&a_position) as usize
70+
};
71+
72+
let a_star = AStarBuilder::init(&neighbours, &distance).build();
73+
74+
code.chars()
75+
.tuple_windows()
76+
.flat_map(|(from, to)| {
77+
let path = a_star
78+
.path(
79+
pad.position(from as u8).unwrap(),
80+
pad.position(to as u8).unwrap(),
81+
)
82+
.unwrap();
83+
let mut directions: Vec<Key> = path
84+
.windows(2)
85+
.map(|pair| Dir(pair[0].direction(&pair[1]).unwrap()))
86+
.collect();
87+
directions.push(Activate);
88+
89+
// println!("{from} {to} -> {:?}", directions.iter().map(|d| d.to_string()).collect::<String>());
90+
91+
directions.into_iter()
92+
})
93+
.collect()
94+
}
95+
}
96+
97+
#[derive(Debug, PartialEq)]
98+
enum Key {
99+
Dir(Direction),
100+
Activate,
101+
}
102+
103+
impl Display for Key {
104+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
105+
let v = match self {
106+
Dir(d) => match d {
107+
Direction::North => "^",
108+
Direction::South => "v",
109+
Direction::East => ">",
110+
Direction::West => "<",
111+
_ => unreachable!(),
112+
},
113+
Activate => "A",
114+
};
115+
116+
write!(f, "{}", v)
117+
}
118+
}
119+
120+
struct Pad {
121+
positions: Positions,
122+
adjacent: Adjacent,
123+
}
124+
125+
impl Pad {
126+
fn numeric() -> Self {
127+
let positions = Self::build_positions(NUM_PAD, &NUM_PAD_ELEMENTS);
128+
let adjacent = Self::build_adjacent(&positions);
129+
130+
Self {
131+
positions,
132+
adjacent,
133+
}
134+
}
135+
136+
fn arrow() -> Self {
137+
let positions = Self::build_positions(ARROW_PAD, &ARROW_PAD_ELEMENTS);
138+
let adjacent = Self::build_adjacent(&positions);
139+
140+
Self {
141+
positions,
142+
adjacent,
143+
}
144+
}
145+
146+
fn build_positions(map: &str, elements: &[u8]) -> Positions {
147+
let num_grid: Grid<u8> = Grid::from_custom(map, |c| c as u8);
148+
let mut num_pad_positions: Positions = HashMap::with_capacity(num_grid.surface().area());
149+
150+
for i in elements {
151+
num_pad_positions.insert(*i, num_grid.get_first_position(i).unwrap());
152+
}
153+
154+
num_pad_positions
155+
}
156+
157+
fn build_adjacent(num_pad_map: &Positions) -> Adjacent {
158+
let mut adjacent_map = HashMap::with_capacity(num_pad_map.len());
159+
160+
for pos in num_pad_map.values() {
161+
let adjacent: Vec<Point> = pos
162+
.adjacent()
163+
.iter()
164+
.filter_map(|p| num_pad_map.iter().find(|(_, v)| **v == *p))
165+
.map(|(_, p)| *p)
166+
.collect();
167+
168+
adjacent_map.insert(*pos, adjacent);
169+
}
170+
171+
adjacent_map
172+
}
173+
174+
fn position(&self, element: u8) -> Option<Point> {
175+
self.positions.get(&element).copied()
176+
}
177+
178+
fn adjacent(&self, position: &Point) -> Vec<Point> {
179+
self.adjacent.get(position).unwrap().to_vec()
180+
}
181+
}
182+
183+
#[cfg(test)]
184+
mod tests {
185+
use crate::solutions::year2024::day21::Key::Activate;
186+
use crate::solutions::year2024::day21::{Day21, Pad};
187+
use crate::solutions::Solution;
188+
189+
const EXAMPLE: &str = r#"029A
190+
980A
191+
179A
192+
456A
193+
379A"#;
194+
195+
#[test]
196+
#[ignore]
197+
fn part_one_example() {
198+
assert_eq!("126384", Day21.part_one(EXAMPLE));
199+
}
200+
201+
#[test]
202+
#[ignore]
203+
fn path_len() {
204+
let pads = vec![Pad::numeric(), Pad::arrow(), Pad::arrow()];
205+
206+
assert_eq!(68, Day21.path_len("029A", &pads));
207+
assert_eq!(60, Day21.path_len("980A", &pads));
208+
assert_eq!(68, Day21.path_len("179A", &pads));
209+
assert_eq!(64, Day21.path_len("456A", &pads));
210+
assert_eq!(64, Day21.path_len("379A", &pads));
211+
}
212+
213+
#[test]
214+
fn path_for_str() {
215+
assert_eq!(
216+
Day21.path_for_str("AA", &Pad::numeric()),
217+
vec![Activate, Activate]
218+
);
219+
}
220+
}

src/solutions/year2024/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ pub mod day16;
1717
pub mod day18;
1818
pub mod day19;
1919
pub mod day20;
20+
pub mod day21;

0 commit comments

Comments
 (0)