Skip to content

Commit af4a70a

Browse files
committed
day21 🤖
1 parent 5ac7807 commit af4a70a

File tree

5 files changed

+306
-7
lines changed

5 files changed

+306
-7
lines changed

2024/README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
# Advent of Code in Rust 🦀
22

33
![AoC2024](https://img.shields.io/badge/Advent_of_Code-2024-8A2BE2)
4-
![Stars: 40](https://img.shields.io/badge/Stars-40⭐-blue)
5-
![Rust: 21](https://img.shields.io/badge/Rust-21-cyan?logo=Rust)
4+
![Stars: 42](https://img.shields.io/badge/Stars-42⭐-blue)
5+
![Rust: 22](https://img.shields.io/badge/Rust-22-cyan?logo=Rust)
66
![Python: 8](https://img.shields.io/badge/Python-8-cyan?logo=Python)
77

8-
## 2024 ([Calendar](https://adventofcode.com/2024)) ([Solutions](../2024/)) : 40
8+
## 2024 ([Calendar](https://adventofcode.com/2024)) ([Solutions](../2024/)) : 42
99

1010
Puzzle | Stars | Languages
1111
---------------------------------------------------------------------- | ----- | -----------
@@ -29,3 +29,4 @@ Puzzle | Stars |
2929
[Day 18: RAM Run](https://adventofcode.com/2024/day/18) | ⭐⭐ | [![Rust](../scripts/assets/rust.png)](../2024/day18/day18.rs) [![C++](../scripts/assets/cpp.png)](../2024/day18/day18.cpp) [![Go](../scripts/assets/go.png)](../2024/day18/day18.go)
3030
[Day 19: Linen Layout](https://adventofcode.com/2024/day/19) | ⭐⭐ | [![Rust](../scripts/assets/rust.png)](../2024/day19/day19.rs)
3131
[Day 20: Race Condition](https://adventofcode.com/2024/day/20) | ⭐⭐ | [![Rust](../scripts/assets/rust.png)](../2024/day20/day20.rs)
32+
[Day 21: Keypad Conundrum](https://adventofcode.com/2024/day/21) | ⭐⭐ | [![Rust](../scripts/assets/rust.png)](../2024/day21/day21.rs)

2024/day21/Cargo.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[package]
2+
name = "day21"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
aoc = { path = "../../aoc" }
8+
itertools = "*"
9+
10+
[[bin]]
11+
name = "day21"
12+
path = "day21.rs"

2024/day21/day21.rs

Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
//! [Day 21: Keypad Conundrum](https://adventofcode.com/2024/day/21)
2+
3+
use itertools::Itertools;
4+
use std::collections::{HashMap, VecDeque};
5+
6+
type ButtonSequences = HashMap<(char, char), Vec<String>>;
7+
8+
fn compute_sequences(keypad: &[&str]) -> ButtonSequences {
9+
let mut positions = HashMap::new();
10+
11+
// size of the keypad
12+
let size_x = i32::try_from(keypad[0].len()).unwrap();
13+
let size_y = i32::try_from(keypad.len()).unwrap();
14+
15+
// positions of each button
16+
for (y, row) in keypad.iter().enumerate() {
17+
for (x, button) in row.chars().enumerate() {
18+
if button != ' ' {
19+
let x = i32::try_from(x).unwrap();
20+
let y = i32::try_from(y).unwrap();
21+
positions.insert(button, (x, y));
22+
}
23+
}
24+
}
25+
26+
// find all paths between each pair of buttons
27+
let mut sequences: ButtonSequences = HashMap::new();
28+
29+
for &from_button in positions.keys() {
30+
for &to_button in positions.keys() {
31+
// same button
32+
if from_button == to_button {
33+
// nota: A to activate/push the button
34+
sequences.insert((from_button, to_button), vec!["A".to_string(); 1]);
35+
continue;
36+
}
37+
let mut possibilities = Vec::new();
38+
let mut queue = VecDeque::new();
39+
let mut shortest = usize::MAX;
40+
let mut visited = HashMap::new();
41+
42+
queue.push_front((positions[&from_button], String::new()));
43+
visited.insert(positions[&from_button], 0);
44+
45+
while let Some(((x, y), moves)) = queue.pop_back() {
46+
// we reach the end
47+
if (x, y) == positions[&to_button] {
48+
if moves.len() < shortest {
49+
shortest = moves.len();
50+
possibilities.clear();
51+
}
52+
53+
if moves.len() == shortest {
54+
possibilities.push(format!("{moves}A"));
55+
}
56+
continue;
57+
}
58+
59+
// try all directions
60+
for (nx, ny, nm) in [
61+
(x - 1, y, '<'),
62+
(x + 1, y, '>'),
63+
(x, y - 1, '^'),
64+
(x, y + 1, 'v'),
65+
] {
66+
// outside the keypad
67+
if nx < 0 || nx >= size_x || ny < 0 || ny >= size_y {
68+
continue;
69+
}
70+
71+
let button = keypad[usize::try_from(ny).unwrap()]
72+
.chars()
73+
.nth(usize::try_from(nx).unwrap())
74+
.unwrap();
75+
76+
// no button
77+
if button == ' ' {
78+
continue;
79+
}
80+
81+
// if not yet visited of found a shorter path
82+
if *visited.get(&(nx, ny)).unwrap_or(&usize::MAX) >= moves.len() {
83+
queue.push_front(((nx, ny), format!("{moves}{nm}")));
84+
visited.insert((nx, ny), moves.len());
85+
}
86+
}
87+
}
88+
89+
sequences.insert((from_button, to_button), possibilities);
90+
}
91+
}
92+
93+
sequences
94+
}
95+
96+
struct Solver {
97+
numerical_sequences: ButtonSequences,
98+
directional_sequences: ButtonSequences,
99+
}
100+
101+
impl Solver {
102+
fn new() -> Self {
103+
// the layout of the numerical keypad
104+
// +---+---+---+
105+
// | 7 | 8 | 9 |
106+
// +---+---+---+
107+
// | 4 | 5 | 6 |
108+
// +---+---+---+
109+
// | 1 | 2 | 3 |
110+
// +---+---+---+
111+
// | 0 | A |
112+
// +---+---+
113+
let numerical_keypad = ["789", "456", "123", " 0A"];
114+
115+
// the layout of the directional keypad
116+
// +---+---+
117+
// | ^ | A |
118+
// +---+---+---+
119+
// | < | v | > |
120+
// +---+---+---+
121+
let directional_keypad = [" ^A", "<v>"];
122+
123+
Self {
124+
numerical_sequences: compute_sequences(&numerical_keypad),
125+
directional_sequences: compute_sequences(&directional_keypad),
126+
}
127+
}
128+
129+
/// find all combinations of sequences to enter the code
130+
fn find_code_seqs(&self, code: &str) -> Vec<String> {
131+
let mut c = Vec::new();
132+
for i in 0..code.len() {
133+
let button_from = if i == 0 {
134+
'A' // we start at button A
135+
} else {
136+
code.chars().nth(i - 1).unwrap()
137+
};
138+
let button_to = code.chars().nth(i).unwrap();
139+
140+
let seqs = self.numerical_sequences[&(button_from, button_to)].clone(); // ways to k1→k2
141+
142+
c.push(seqs);
143+
}
144+
145+
c.iter()
146+
.multi_cartesian_product()
147+
.map(|k| k.iter().join(""))
148+
.collect::<Vec<_>>()
149+
}
150+
151+
/// compute recursively the length of the sequence to play the `targetted_seq`
152+
/// with `robots` that control directional keypads
153+
fn compute_seq_length(
154+
&self,
155+
targetted_seq: &str,
156+
robots: u32,
157+
cache: &mut HashMap<(String, u32), u64>,
158+
) -> u64 {
159+
if let Some(found) = cache.get(&(targetted_seq.to_string(), robots)) {
160+
return *found;
161+
}
162+
163+
if robots <= 1 {
164+
return (0..targetted_seq.len())
165+
.map(|i| {
166+
let k1 = if i == 0 {
167+
'A'
168+
} else {
169+
targetted_seq.chars().nth(i - 1).unwrap()
170+
};
171+
let k2 = targetted_seq.chars().nth(i).unwrap();
172+
173+
// all seqs have same length
174+
self.directional_sequences[&(k1, k2)][0].len() as u64
175+
})
176+
.sum();
177+
}
178+
179+
let result = (0..targetted_seq.len())
180+
.map(|i| {
181+
let button_from = if i == 0 {
182+
'A'
183+
} else {
184+
targetted_seq.chars().nth(i - 1).unwrap()
185+
};
186+
let button_to = targetted_seq.chars().nth(i).unwrap();
187+
188+
self.directional_sequences[&(button_from, button_to)]
189+
.iter()
190+
.map(|seq| self.compute_seq_length(seq, robots - 1, cache))
191+
.min()
192+
.unwrap()
193+
})
194+
.sum();
195+
196+
cache.insert((targetted_seq.to_string(), robots), result);
197+
198+
result
199+
}
200+
201+
/// computes the compleixity of to enter `code` with a chain of `robots` robots
202+
fn complexity(&self, code: &str, robots: u32) -> u64 {
203+
let seqs = self.find_code_seqs(code);
204+
205+
let mut cache = HashMap::new();
206+
207+
let min_length = seqs
208+
.iter()
209+
.map(|seq| self.compute_seq_length(seq, robots, &mut cache))
210+
.min()
211+
.unwrap();
212+
213+
let num_code = code
214+
.chars()
215+
.map_while(|c| c.to_digit(10))
216+
.fold(0, |acc, d| acc * 10 + d);
217+
218+
min_length * u64::from(num_code)
219+
}
220+
}
221+
222+
struct Puzzle {
223+
codes: Vec<String>,
224+
solver: Solver,
225+
}
226+
227+
impl Puzzle {
228+
fn new() -> Puzzle {
229+
Puzzle {
230+
codes: Vec::new(),
231+
solver: Solver::new(),
232+
}
233+
}
234+
235+
/// Get the puzzle input.
236+
fn configure(&mut self, path: &str) {
237+
let data = std::fs::read_to_string(path).unwrap();
238+
239+
for line in data.lines() {
240+
self.codes.push(line.to_string());
241+
}
242+
}
243+
244+
/// Solve part one.
245+
fn part1(&self) -> u64 {
246+
self.codes
247+
.iter()
248+
.map(|code| self.solver.complexity(code, 2))
249+
.sum()
250+
}
251+
252+
/// Solve part two.
253+
fn part2(&self) -> u64 {
254+
self.codes
255+
.iter()
256+
.map(|code| self.solver.complexity(code, 25))
257+
.sum()
258+
}
259+
}
260+
261+
fn main() {
262+
let args = aoc::parse_args();
263+
let mut puzzle = Puzzle::new();
264+
puzzle.configure(args.path.as_str());
265+
println!("{}", puzzle.part1());
266+
println!("{}", puzzle.part2());
267+
}
268+
269+
/// Test from puzzle input
270+
#[cfg(test)]
271+
mod test {
272+
use super::*;
273+
274+
#[test]
275+
fn test01() {
276+
let mut puzzle = Puzzle::new();
277+
puzzle.configure("test.txt");
278+
assert_eq!(puzzle.part1(), 126384);
279+
}
280+
}

2024/day21/test.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
029A
2+
980A
3+
179A
4+
456A
5+
379A

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
# [Advent of Code](https://adventofcode.com) in Rust 🦀
22

3-
![Stars: 490](https://img.shields.io/badge/Stars-490⭐-blue)
4-
![Rust: 195](https://img.shields.io/badge/Rust-195-cyan?logo=Rust)
3+
![Stars: 492](https://img.shields.io/badge/Stars-492⭐-blue)
4+
![Rust: 196](https://img.shields.io/badge/Rust-196-cyan?logo=Rust)
55
![Python: 121](https://img.shields.io/badge/Python-121-cyan?logo=Python)
66

77
Solutions of [Advent of Code](https://adventofcode.com/) in [Rust](https://www.rust-lang.org), and sometimes in [Python](https://www.python.org/) and other languages.
88

99
Made for fun 😎 and to practice Rust. Many thanks to [Eric Wastl](https://twitter.com/ericwastl).
1010

11-
## 2024 (current event) ([Calendar](https://adventofcode.com/2024)) ([Solutions](2024/)) : 40
11+
## 2024 (current event) ([Calendar](https://adventofcode.com/2024)) ([Solutions](2024/)) : 42
1212

1313
Puzzle | Stars | Languages
1414
---------------------------------------------------------------------- | ----- | -----------
@@ -32,12 +32,13 @@ Puzzle | Stars |
3232
[Day 18: RAM Run](https://adventofcode.com/2024/day/18) | ⭐⭐ | [![Rust](./scripts/assets/rust.png)](./2024/day18/day18.rs) [![C++](./scripts/assets/cpp.png)](./2024/day18/day18.cpp) [![Go](./scripts/assets/go.png)](./2024/day18/day18.go)
3333
[Day 19: Linen Layout](https://adventofcode.com/2024/day/19) | ⭐⭐ | [![Rust](./scripts/assets/rust.png)](./2024/day19/day19.rs)
3434
[Day 20: Race Condition](https://adventofcode.com/2024/day/20) | ⭐⭐ | [![Rust](./scripts/assets/rust.png)](./2024/day20/day20.rs)
35+
[Day 21: Keypad Conundrum](https://adventofcode.com/2024/day/21) | ⭐⭐ | [![Rust](./scripts/assets/rust.png)](./2024/day21/day21.rs)
3536

3637
## All years
3738

3839
Calendar | Solutions | Stars | Rust | Python | 🎄
3940
-------- | --------- | ----- | ---- | ------ | --
40-
[Advent of Code 2024](https://adventofcode.com/2024) | [Solutions](2024/README.md) | 40⭐ | 20 | 8 | 3
41+
[Advent of Code 2024](https://adventofcode.com/2024) | [Solutions](2024/README.md) | 42⭐ | 21 | 8 | 3
4142
[Advent of Code 2023](https://adventofcode.com/2023) | [Solutions](2023/README.md) | 50⭐ | 24 | 11 | 1
4243
[Advent of Code 2022](https://adventofcode.com/2022) | [Solutions](2022/README.md) | 50⭐ | 24 | 18 |
4344
[Advent of Code 2021](https://adventofcode.com/2021) | [Solutions](2021/README.md) | 50⭐ | 23 | 12 |

0 commit comments

Comments
 (0)