Skip to content

Commit 949c01a

Browse files
committed
Rust/2024/21: add solution
1 parent d98aaf9 commit 949c01a

File tree

8 files changed

+191
-3
lines changed

8 files changed

+191
-3
lines changed

README.md

+2-2
Large diffs are not rendered by default.

Rust/2024/21.rs

+172
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
#![feature(test)]
2+
3+
use std::iter;
4+
5+
use aoc::matrix;
6+
use itertools::Itertools;
7+
use rustc_hash::FxHashSet;
8+
9+
type Input = Vec<Vec<u8>>;
10+
11+
fn setup(input: &str) -> Input {
12+
input
13+
.trim()
14+
.lines()
15+
.map(|l| {
16+
l.bytes()
17+
.map(|b| match b {
18+
b'0'..=b'9' => b - b'0',
19+
b'A' => 10,
20+
_ => panic!(),
21+
})
22+
.collect()
23+
})
24+
.collect()
25+
}
26+
27+
type Sequence = Vec<u8>;
28+
29+
/// For each keypad button pair compute all optimal sequences to activate the
30+
/// second button, starting at the first one.
31+
fn compute_sequences<const N: usize>(keypad: [(u8, u8); N]) -> [[Vec<Sequence>; N]; N] {
32+
keypad.map(|(i1, j1)| {
33+
keypad.map(|(i2, j2)| {
34+
iter::empty()
35+
.chain(iter::repeat_n(0, i1.saturating_sub(i2) as _))
36+
.chain(iter::repeat_n(1, j1.saturating_sub(j2) as _))
37+
.chain(iter::repeat_n(2, i2.saturating_sub(i1) as _))
38+
.chain(iter::repeat_n(3, j2.saturating_sub(j1) as _))
39+
.permutations((i1.abs_diff(i2) + j1.abs_diff(j2)) as _)
40+
.filter(|seq| {
41+
let (mut i, mut j) = (i1, j1);
42+
seq.iter().all(|&d| {
43+
match d {
44+
0 => i -= 1,
45+
1 => j -= 1,
46+
2 => i += 1,
47+
3 => j += 1,
48+
_ => panic!(),
49+
}
50+
keypad.contains(&(i, j))
51+
})
52+
})
53+
.collect::<FxHashSet<_>>()
54+
.into_iter()
55+
.map(|mut s| {
56+
s.push(DIR_A);
57+
s
58+
})
59+
.collect()
60+
})
61+
})
62+
}
63+
64+
const NUM_A: u8 = 10;
65+
const DIR_A: u8 = 4;
66+
67+
type NumSeqs = [[Vec<Sequence>; 11]; 11];
68+
type DirSeqs = [[Vec<Sequence>; 5]; 5];
69+
70+
const NUM_KEYPAD: [(u8, u8); 11] = [
71+
(3, 1), // 0
72+
(2, 0), // 1
73+
(2, 1), // 2
74+
(2, 2), // 3
75+
(1, 0), // 4
76+
(1, 1), // 5
77+
(1, 2), // 6
78+
(0, 0), // 7
79+
(0, 1), // 8
80+
(0, 2), // 9
81+
(3, 2), // A
82+
];
83+
84+
const DIR_KEYPAD: [(u8, u8); 5] = [
85+
(0, 1), // ^
86+
(1, 0), // <
87+
(1, 1), // v
88+
(1, 2), // >
89+
(0, 2), // A
90+
];
91+
92+
fn steps(init: u8, seq: &[u8]) -> impl Iterator<Item = (usize, usize)> + use<'_> {
93+
iter::once(init as _)
94+
.chain(seq.iter().map(|&x| x as _))
95+
.tuple_windows()
96+
}
97+
98+
type DirMatrix = [[usize; 5]; 5];
99+
100+
/// For each button pair on the directional keypad closest to the door compute
101+
/// the minimum number of button presses needed on the manually controlled
102+
/// directional keypad to activate the second button starting at the first one.
103+
fn compute_dir_matrix<const N: usize>(dir_seqs: &DirSeqs) -> DirMatrix {
104+
(1..N).fold(
105+
// if there is only one robot controlled directional keypad, use one of the pre-computed
106+
// sequences on the human controlled keypad. doesn't matter which one, they should all have
107+
// the same (optimal) length.
108+
matrix(|x, y| dir_seqs[x][y][0].len()),
109+
// if there are more robot controlled directional keypads, compute a new matrix based on
110+
// the previous one: try all sequences from x to y on the directional keypad, sum up all
111+
// the required button presses for each sequence and find the minimum of those.
112+
|prev, _| {
113+
matrix(|x, y| {
114+
dir_seqs[x][y]
115+
.iter()
116+
.map(|seq| steps(DIR_A, seq).map(|(x, y)| prev[x][y]).sum())
117+
.min()
118+
.unwrap()
119+
})
120+
},
121+
)
122+
}
123+
124+
type NumMatrix = [[usize; 11]; 11];
125+
126+
/// Similarly to [`compute_dir_matrix`], for each button pair on the numeric
127+
/// keypad at the door compute the minimum number of button presses needed on
128+
/// the manually controlled directional keypad to activate the second button
129+
/// starting at the first one.
130+
fn compute_num_matrix(num_seqs: &NumSeqs, dir_matrix: &DirMatrix) -> NumMatrix {
131+
matrix(|x, y| {
132+
// try all sequences from x to y on the numeric keypad, sum up all the required
133+
// button presses for each sequence and find the minimum of those.
134+
num_seqs[x][y]
135+
.iter()
136+
.map(|seq| steps(DIR_A, seq).map(|(x, y)| dir_matrix[x][y]).sum())
137+
.min()
138+
.unwrap()
139+
})
140+
}
141+
142+
fn min_len(seq: &[u8], num_matrix: &NumMatrix) -> usize {
143+
steps(NUM_A, seq).map(|(x, y)| num_matrix[x][y]).sum()
144+
}
145+
146+
fn numeric_part(seq: &[u8]) -> usize {
147+
seq.iter()
148+
.filter(|&&b| b < 10)
149+
.fold(0, |acc, &b| acc * 10 + b as usize)
150+
}
151+
152+
fn solve<const N: usize>(input: &Input) -> usize {
153+
let num_seqs = compute_sequences(NUM_KEYPAD);
154+
let dir_seqs = compute_sequences(DIR_KEYPAD);
155+
let dir_matrix = compute_dir_matrix::<N>(&dir_seqs);
156+
let num_matrix = compute_num_matrix(&num_seqs, &dir_matrix);
157+
158+
input
159+
.iter()
160+
.map(|seq| min_len(seq, &num_matrix) * numeric_part(seq))
161+
.sum()
162+
}
163+
164+
fn part1(input: &Input) -> usize {
165+
solve::<2>(input)
166+
}
167+
168+
fn part2(input: &Input) -> usize {
169+
solve::<25>(input)
170+
}
171+
172+
aoc::main!(2024, 21, ex: 1);

Rust/2024/main.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ aoc::year! {
1919
"18.rs",
2020
"19.rs",
2121
"20.rs",
22-
// "21.rs",
22+
"21.rs",
2323
// "22.rs",
2424
// "23.rs",
2525
// "24.rs",

Rust/Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -420,3 +420,6 @@ path = "2024/19.rs"
420420
[[bin]]
421421
name = "2024_20"
422422
path = "2024/20.rs"
423+
[[bin]]
424+
name = "2024_21"
425+
path = "2024/21.rs"

Rust/lib/lib.rs

+6
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ pub mod tuples;
1515

1616
extern crate test;
1717

18+
pub fn matrix<const N: usize, const M: usize>(
19+
mut f: impl FnMut(usize, usize) -> usize,
20+
) -> [[usize; M]; N] {
21+
std::array::from_fn(|x| std::array::from_fn(|y| f(x, y)))
22+
}
23+
1824
#[macro_export]
1925
macro_rules! main {
2026
($year:literal, $day:tt $(, ex: $($example:literal $([$expart:ident])?),*)?) => {

examples/2024/21/1

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
029A
2+
980A
3+
179A
4+
456A
5+
379A

examples/2024/21/1.1

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
126384

examples/2024/21/1.2

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
154115708116294

0 commit comments

Comments
 (0)