generated from bravit/advent-of-code-rust-template
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathday_21.rs
377 lines (300 loc) · 12.9 KB
/
day_21.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
use std::collections::HashMap;
use std::hash::Hash;
use std::iter;
use anyhow::*;
use itertools::Itertools;
use crate::{Solution};
use crate::tools::IntReader;
const TEST: &str = "\
029A
980A
179A
456A
379A";
/// Digit code representation, as 3-digits and as value
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
struct Code {
digits: [u8; 3],
value: u32,
}
/// An entry on the numerical keypad
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
enum NumericalEntry {
Digit (u8),
Activate,
}
/// An entry on the directional keypad
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
enum DirectionalEntry {
Left,
Right,
Up,
Down,
Activate,
}
/// A single movement between a pair of directional entries
type StartDest = (DirectionalEntry, DirectionalEntry);
/// Memoize on the movements, for different depth of robot indirections
type MemoKey = (StartDest, usize);
/// Memoization of the best sequence length, for different movements and indirection depths
type Memo = HashMap<MemoKey, usize>;
/// Models the *numerical* keypad with the position of the robot's arm manipulating it
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
struct NumericalKeypad {
pos: NumericalEntry
}
/// Models a *directional* keypad with the position of the robot's arm manipulating it
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
struct DirectionalKeypad {
pos: DirectionalEntry
}
/// Given an initial `from` coordinate on a key pad, and a target coordinate `to`, compute
/// the two shortest-path sequence we have to consider to navigate in between:
/// * First sequence: all-vertical, then all-horizontal (e.g, from 0 to 9: `^^^>A`)
/// * Second sequence: all-horizontal, then all-vertical (e.g, from 0 to 9: `>^^^A`)
///
/// There are more than 2 shortest-path sequences, but others could never be part of any global
/// solution. The reason is that, due to upper level of indirection (through robots), breaking
/// a sequence like `>^^^` in something like `^>^^` becomes highly
/// inefficient: At the upper level, the robot would have to navigate on `>` then come back to `^`
/// without benefiting of positions where no movement is required.
///
/// **The two sequences returned by this function include the final activation that is required
/// to actually press the button.**
fn get_raw_sequences_from_coordinates (from: (u8, u8), to: (u8, u8))
-> [impl Iterator<Item = DirectionalEntry>; 2] {
// Compute the delta between the pair of keypad coordinates
let (row_0, col_0) = from;
let (row_1, col_1) = to;
let row_diff = (row_1 as i8 - row_0 as i8).abs () as usize;
let col_diff = (col_1 as i8 - col_0 as i8).abs () as usize;
// Determine if we need to go up or down in the vertical axis. Same for left and right.
let v_dir = if row_0 < row_1 { DirectionalEntry::Up } else { DirectionalEntry::Down };
let h_dir = if col_0 < col_1 { DirectionalEntry::Right } else { DirectionalEntry::Left };
// Simple sequence of "all vertical" movement. Same for the "all horizontal" displacement.
let vertical = iter::repeat(v_dir).take(row_diff);
let horizontal = iter::repeat(h_dir).take(col_diff);
// We only consider two sequences to reach the requested entry:
// * an all-vertical then all-horizontal sequence
// * an all-horizontal then all-vertical sequence
let sequence_0 = vertical.clone()
.chain(horizontal.clone ())
.chain(iter::once(DirectionalEntry::Activate));
let sequence_1 = horizontal
.chain(vertical)
.chain(iter::once(DirectionalEntry::Activate));
[sequence_0, sequence_1]
}
/// This function is similar to [get_raw_sequences_from_coordinates] but do not return
/// a sequence that would require to move the robot arm above the `empty_button`.
fn get_sequences_from_coordinates (
from: (u8, u8),
to: (u8, u8),
empty_button: (u8, u8))
-> Vec<Vec<DirectionalEntry>> {
let [sequence_0, sequence_1] =
get_raw_sequences_from_coordinates(from, to);
let row_diff = (to.0 as i8 - from.0 as i8).abs () as usize;
let col_diff = (to.1 as i8 - from.1 as i8).abs () as usize;
let sequence_0: Vec<DirectionalEntry> = sequence_0.collect();
let sequence_1: Vec<DirectionalEntry> = sequence_1.collect();
// Avoid the vert-horz sequence if we would have to go above the empty button
// (start column and destination row cross above it)
let (avoid_row, avoid_col) = empty_button;
let avoid_seq_0 = from.1 == avoid_col && to.0 == avoid_row;
// Avoid the horz-vert sequence if we would have to go above the empty button
// (start row and destination column cross above it)
let avoid_seq_1 = from.0 == avoid_row && to.1 == avoid_col;
match (row_diff, col_diff) {
// Avoid returning two identical sequences for full horizontal or vertical movements
(0, _) | (_, 0) => vec![sequence_0],
// Otherwise, return the two sequences provided they do not overlap the empty button
_ => {
match (avoid_seq_0, avoid_seq_1) {
(false, false) => vec![sequence_0, sequence_1],
(true, false) => vec![sequence_1],
(false, true) => vec![sequence_0],
_ => unreachable!(),
}
}
}
}
impl DirectionalKeypad {
/// New directional keypad instance, arm starting on the *Activate* button
fn new () -> Self {
Self { pos: DirectionalEntry::Activate }
}
/// Given the current robot's arm position, return the different directional sequences
/// that enable to reach the provided `entry` button and to press it.
///
/// For example, going from `<` to `A` would return this sequence:
/// * `>>^A`
///
/// **This function updates the current robot's arm position**
fn get_sequences_to (&mut self, entry: DirectionalEntry) -> Vec<Vec<DirectionalEntry>> {
// Get the coordinates of the current arm position and of the final position
let from = Self::entry_to_row_col(self.pos);
let to = Self::entry_to_row_col(entry);
// Update the robot arm position
self.pos = entry;
const EMPTY_BUTTON: (u8, u8) = (1, 0);
get_sequences_from_coordinates(from, to, EMPTY_BUTTON)
}
/// Return the (row, column) coordinate of a button. The *Left key* is in `(0, 0)`
fn entry_to_row_col(entry: DirectionalEntry) -> (u8, u8) {
match entry {
DirectionalEntry::Activate => (1, 2),
DirectionalEntry::Left => (0, 0),
DirectionalEntry::Right => (0, 2),
DirectionalEntry::Down => (0, 1),
DirectionalEntry::Up => (1, 1),
}
}
}
impl NumericalKeypad {
/// New numerical keypad instance, arm starting on the *Activate* button
fn new () -> Self {
Self { pos: NumericalEntry::Activate }
}
/// Given the current robot's arm position, return the different directional sequences
/// that enable to reach the provided `entry` button and to press it.
///
/// For example, going from `1` to `8` would return this sequence:
/// * `>^^A`
/// * `^^>A`
///
/// **This function updates the current robot's arm position**
fn get_sequences_to (&mut self, entry: NumericalEntry) -> Vec<Vec<DirectionalEntry>> {
// Get the coordinates of the current arm position and of the final position
let from = Self::entry_to_row_col(self.pos);
let to = Self::entry_to_row_col(entry);
// Update the robot arm position
self.pos = entry;
const EMPTY_BUTTON: (u8, u8) = (0, 0);
get_sequences_from_coordinates(from, to, EMPTY_BUTTON)
}
/// Return the (row, column) coordinate of a button. The *Activation key* is in `(0, 2)`
fn entry_to_row_col(entry: NumericalEntry) -> (u8, u8) {
match entry {
NumericalEntry::Activate => (0, 2),
NumericalEntry::Digit (0) => (0, 1),
NumericalEntry::Digit (d) => {
let row = 1 + (d-1) / 3;
let col = (d-1) % 3;
(row, col)
},
}
}
}
fn split (content: &str) -> Vec<&str> {
content.lines().collect()
}
/// Load the different codes we have to deal with from the puzzle file `content`
fn load_codes (content: &[&str]) -> Result<Vec<Code>> {
let mut reader= IntReader::new(false);
content.iter().map(|&row| {
let raw: [u32; 1] = reader.process_row_fix(row)
.ok_or(anyhow!("Invalid code: {}", row))?;
let digits = [
(raw[0] / 100) as u8,
((raw[0] / 10) % 10) as u8,
(raw[0] % 10) as u8,
];
Ok(Code { digits, value: raw [0] })
}).collect()
}
/// Given a `current_pos` and a `sequence` of entries on a keypad, return an iterator
/// over the different movement to execute.
/// ## Example
/// ```
/// current_pos: A
/// sequence: [1, 8, 0]
/// return: [(A, 1), (1, 8), (8, 0)]
/// ```
fn sequence_to_movements<'a> (current_pos: DirectionalEntry, sequence: &'a [DirectionalEntry]) -> impl Iterator<Item = StartDest> + 'a{
let seq = iter::once (current_pos).chain(sequence.iter ().copied());
seq.tuple_windows()
}
/// Compute the length of the shortest sequence that enables to make a single `movement` on the
/// numerical keypad through a chain of `depth` robots.
fn compute_move_length_through_robot_chain(memo: &mut Memo, movement: StartDest, depth: usize) -> usize {
// No robot to consider, we can do the movement ourselves in one step
if depth == 0 { return 1; }
// Consult the table and return the value if we know it
let memo_key = (movement, depth);
if let Some (length) = memo.get(&memo_key) { return *length; }
// Otherwise, consider the first robot from the chain and get the different
// sequences that would enable to execute the movement
let mut robot = DirectionalKeypad::new();
robot.pos = movement.0;
let sequences = robot.get_sequences_to(movement.1);
// Analyze each of such sequence and keep the best one
let min_length = sequences.iter().map (|seq| {
// The current sequence is split into a succession of movements.
// We recurse on each of them and sum up everything
sequence_to_movements(DirectionalEntry::Activate, seq).map (|movement| {
compute_move_length_through_robot_chain(memo, movement, depth-1)
}).sum ()
}).min().unwrap();
// Save the computed value
memo.insert(memo_key, min_length);
// And return the shortest sequence length
min_length
}
/// Compute the length of the shortest sequence that enables to enter the provided `code`
/// on the numerical keypad. Parameter `depth` gives the number of intermediate robots
/// between the final numerical keypad and the robot we manipulate ourselves.
/// (i.e: 2 for part 1, 25 for part 2)
fn compute_min_sequence_length(memo: &mut Memo, code: Code, depth: usize) -> usize {
let mut num_key = NumericalKeypad::new();
// Set up the sequence of buttons to press on the numerical keypad to enter the code
let digit_seq = code.digits.iter()
.map(|&d| {NumericalEntry::Digit (d)})
.chain(iter::once(NumericalEntry::Activate));
// Sum the length required for each digit
let mut total_length = 0;
digit_seq.for_each(|entry| {
// Get all possible sequences to reach each digit.
// Take the min of such sequences to get the digit sequence length
let sequences = num_key.get_sequences_to(entry);
let min_length: usize = sequences.iter ().map (|seq| {
// For each sequence, we decompose into a succession of moves. We sum
// the length of the best sequence for each move
sequence_to_movements(DirectionalEntry::Activate, seq).map (|movement| {
compute_move_length_through_robot_chain(memo, movement, depth)
}).sum ()
}).min().unwrap();
total_length += min_length;
});
total_length
}
/// Solve first part of the puzzle
fn part_a (content: &[&str]) -> Result<usize> {
const DEPTH: usize = 2;
let codes = load_codes(content)?;
let mut memo = Memo::new();
let mut complexity = 0;
for code in codes {
let seq_len = compute_min_sequence_length(&mut memo, code, DEPTH);
complexity += seq_len * code.value as usize;
}
Ok(complexity)
}
/// Solve second part of the puzzle
fn part_b (content: &[&str]) -> Result<usize> {
const DEPTH: usize = 25;
let codes = load_codes(content)?;
let mut memo = Memo::new();
let mut complexity = 0;
for code in codes {
let seq_len = compute_min_sequence_length(&mut memo, code, DEPTH);
complexity += seq_len * code.value as usize;
}
Ok(complexity)
}
pub fn day_21 (content: &[&str]) -> Result <(Solution, Solution)> {
debug_assert!(part_a (&split(TEST)).unwrap_or_default() == 126384);
let ra = part_a(content)?;
let rb = part_b(content)?;
Ok((Solution::Unsigned(ra), Solution::Unsigned(rb)))
}