272 lines
8.4 KiB
Rust
272 lines
8.4 KiB
Rust
use crate::util::maps::Direction;
|
|
use std::collections::HashMap;
|
|
|
|
const DPADS: usize = 25;
|
|
|
|
pub fn day21() {
|
|
let sequences: Vec<String> = crate::input(21).lines().map(|l| l.to_string()).collect();
|
|
|
|
let mut memo: HashMap<(String, usize), u64> = HashMap::new();
|
|
fn deeper(s: String, depth: usize, memo: &mut HashMap<(String, usize), u64>) -> u64 {
|
|
// if this is already in deep lut, return
|
|
if let Some(&expanded_size) = memo.get(&(s.to_string(), depth)) {
|
|
return expanded_size;
|
|
}
|
|
// if this is two characters at the end, add expanded size to deep lut, and return
|
|
if depth == 1 {
|
|
let expanded_size = expand_dpad_string(&s).len() as u64;
|
|
memo.insert((s, depth), expanded_size);
|
|
return expanded_size;
|
|
}
|
|
// otherwise 1. expand, 2. split into substrings, and 3. recur for each substring
|
|
let expanded = expand_dpad_string(&s);
|
|
// 2. get substrings split by A (but including the A characters at the end of each substring)
|
|
let substrings: Vec<String> = expanded
|
|
.split_terminator("A")
|
|
.map(|substring| {
|
|
let mut substring = substring.to_string();
|
|
substring.push_str("A");
|
|
substring
|
|
})
|
|
.collect();
|
|
let mut total: u64 = 0;
|
|
for substring in substrings {
|
|
let final_expanded_size = deeper(substring, depth - 1, memo);
|
|
total += final_expanded_size;
|
|
}
|
|
memo.insert((s, depth), total);
|
|
total
|
|
}
|
|
|
|
let mut complexity: u64 = 0;
|
|
for sequence in sequences.iter() {
|
|
let first_dpad_steps = expand_numpad_string(sequence);
|
|
let length = deeper(first_dpad_steps.clone(), DPADS, &mut memo);
|
|
let numeric = sequence
|
|
.chars()
|
|
.filter(|c| c.is_numeric())
|
|
.collect::<String>()
|
|
.parse::<u64>()
|
|
.unwrap();
|
|
complexity += length * numeric;
|
|
}
|
|
|
|
println!("Total Complexity: {}", complexity);
|
|
}
|
|
|
|
fn expand_dpad_string(s: &str) -> String {
|
|
let sequence: Vec<usize> = s.chars().map(char_to_directional_keypad_index).collect();
|
|
sequence_to_dpad_steps(
|
|
&directional_keypad(),
|
|
sequence,
|
|
char_to_directional_keypad_index('A'),
|
|
)
|
|
}
|
|
|
|
fn expand_numpad_string(s: &str) -> String {
|
|
let sequence: Vec<usize> = s.chars().map(char_to_numerical_keypad_index).collect();
|
|
sequence_to_dpad_steps(
|
|
&numerical_keypad(),
|
|
sequence,
|
|
char_to_numerical_keypad_index('A'),
|
|
)
|
|
}
|
|
|
|
fn sequence_to_dpad_steps(keypad: &Keypad, sequence: Vec<usize>, start: usize) -> String {
|
|
let mut sequence = sequence.clone();
|
|
sequence.insert(0, start);
|
|
|
|
let mut total_path = "".to_string();
|
|
|
|
for w in sequence.windows(2) {
|
|
let best_path = shortest_paths(keypad, w[0], w[1])
|
|
.into_iter()
|
|
// convert paths to strings
|
|
.map(|path| path.iter().map(direction_to_char).collect::<String>())
|
|
// get the most efficient path (there are certain arbitrary rules for efficiency)
|
|
.max_by_key(|path| efficiency_score(path.as_str()))
|
|
.unwrap();
|
|
total_path.push_str(&best_path);
|
|
total_path.push_str("A");
|
|
}
|
|
|
|
total_path
|
|
}
|
|
|
|
fn shortest_paths(keypad: &Keypad, start: usize, end: usize) -> Vec<Vec<Direction>> {
|
|
fn path_costs(keypad: &Keypad, p: usize, cost_so_far: u64, costs: &mut HashMap<usize, u64>) {
|
|
if cost_so_far < costs.get(&p).copied().unwrap_or(u64::MAX) {
|
|
costs.insert(p, cost_so_far);
|
|
} else {
|
|
return;
|
|
}
|
|
for &adjacent in keypad.adjacents(p) {
|
|
path_costs(keypad, adjacent, cost_so_far + 1, costs);
|
|
}
|
|
}
|
|
let mut costs: HashMap<usize, u64> = HashMap::new();
|
|
path_costs(keypad, end, 0, &mut costs);
|
|
|
|
fn find_paths(
|
|
keypad: &Keypad,
|
|
start: usize,
|
|
costs: &HashMap<usize, u64>,
|
|
) -> Vec<Vec<Direction>> {
|
|
if costs[&start] == 0 {
|
|
return vec![vec![]];
|
|
}
|
|
keypad.buttons[start]
|
|
.links
|
|
.iter()
|
|
.filter(|(_, adjacent)| costs[adjacent] < costs[&start])
|
|
.flat_map(|(&direction, &adjacent)| {
|
|
let mut paths = find_paths(keypad, adjacent, costs);
|
|
paths.iter_mut().for_each(|path| {
|
|
path.insert(0, direction);
|
|
});
|
|
paths
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
find_paths(keypad, start, &costs)
|
|
}
|
|
|
|
fn efficiency_score(s: &str) -> u64 {
|
|
let repetition_score = s
|
|
.chars()
|
|
.collect::<Vec<char>>()
|
|
.windows(2)
|
|
.filter(|w| w[0] == w[1])
|
|
.count() as u64;
|
|
let mut order_mishaps: u64 = 0;
|
|
for ss in s.split("A") {
|
|
order_mishaps += ss.matches(">v").count() as u64;
|
|
order_mishaps += ss.matches("^<").count() as u64;
|
|
order_mishaps += ss.matches("v<").count() as u64;
|
|
order_mishaps += ss.matches(">^").count() as u64;
|
|
}
|
|
|
|
(repetition_score << 32) | (u32::MAX as u64 - order_mishaps)
|
|
}
|
|
|
|
struct Keypad {
|
|
buttons: Vec<Button>,
|
|
}
|
|
|
|
impl Keypad {
|
|
fn adjacents(&self, button: usize) -> impl Iterator<Item = &usize> {
|
|
self.buttons[button].links.values()
|
|
}
|
|
}
|
|
|
|
#[derive(Default, Clone)]
|
|
struct Button {
|
|
links: HashMap<Direction, usize>,
|
|
}
|
|
|
|
fn char_to_numerical_keypad_index(c: char) -> usize {
|
|
match c {
|
|
'7' => 0,
|
|
'8' => 1,
|
|
'9' => 2,
|
|
'4' => 3,
|
|
'5' => 4,
|
|
'6' => 5,
|
|
'1' => 6,
|
|
'2' => 7,
|
|
'3' => 8,
|
|
'0' => 9,
|
|
'A' => 10,
|
|
_ => panic!("The given char {c} has no place on a numerical keypad."),
|
|
}
|
|
}
|
|
|
|
fn char_to_directional_keypad_index(c: char) -> usize {
|
|
match c {
|
|
'^' => 0,
|
|
'A' => 1,
|
|
'<' => 2,
|
|
'v' => 3,
|
|
'>' => 4,
|
|
_ => panic!("The given char {c} has no place on a directional keypad."),
|
|
}
|
|
}
|
|
|
|
fn numerical_keypad() -> Keypad {
|
|
let mut buttons = vec![Button::default(); 11];
|
|
// 7
|
|
buttons[0].links.insert(Direction::East, 1);
|
|
buttons[0].links.insert(Direction::South, 3);
|
|
// 8
|
|
buttons[1].links.insert(Direction::West, 0);
|
|
buttons[1].links.insert(Direction::East, 2);
|
|
buttons[1].links.insert(Direction::South, 4);
|
|
// 9
|
|
buttons[2].links.insert(Direction::West, 1);
|
|
buttons[2].links.insert(Direction::South, 5);
|
|
// 4
|
|
buttons[3].links.insert(Direction::East, 4);
|
|
buttons[3].links.insert(Direction::North, 0);
|
|
buttons[3].links.insert(Direction::South, 6);
|
|
// 5
|
|
buttons[4].links.insert(Direction::West, 3);
|
|
buttons[4].links.insert(Direction::East, 5);
|
|
buttons[4].links.insert(Direction::South, 7);
|
|
buttons[4].links.insert(Direction::North, 1);
|
|
// 6
|
|
buttons[5].links.insert(Direction::West, 4);
|
|
buttons[5].links.insert(Direction::South, 8);
|
|
buttons[5].links.insert(Direction::North, 2);
|
|
// 1
|
|
buttons[6].links.insert(Direction::North, 3);
|
|
buttons[6].links.insert(Direction::East, 7);
|
|
// 2
|
|
buttons[7].links.insert(Direction::West, 6);
|
|
buttons[7].links.insert(Direction::East, 8);
|
|
buttons[7].links.insert(Direction::North, 4);
|
|
buttons[7].links.insert(Direction::South, 9);
|
|
// 3
|
|
buttons[8].links.insert(Direction::North, 5);
|
|
buttons[8].links.insert(Direction::West, 7);
|
|
buttons[8].links.insert(Direction::South, 10);
|
|
// 0
|
|
buttons[9].links.insert(Direction::North, 7);
|
|
buttons[9].links.insert(Direction::East, 10);
|
|
// A
|
|
buttons[10].links.insert(Direction::West, 9);
|
|
buttons[10].links.insert(Direction::North, 8);
|
|
|
|
Keypad { buttons }
|
|
}
|
|
|
|
fn directional_keypad() -> Keypad {
|
|
let mut buttons = vec![Button::default(); 5];
|
|
// ^
|
|
buttons[0].links.insert(Direction::East, 1);
|
|
buttons[0].links.insert(Direction::South, 3);
|
|
// A
|
|
buttons[1].links.insert(Direction::West, 0);
|
|
buttons[1].links.insert(Direction::South, 4);
|
|
// <
|
|
buttons[2].links.insert(Direction::East, 3);
|
|
// v
|
|
buttons[3].links.insert(Direction::East, 4);
|
|
buttons[3].links.insert(Direction::North, 0);
|
|
buttons[3].links.insert(Direction::West, 2);
|
|
// >
|
|
buttons[4].links.insert(Direction::North, 1);
|
|
buttons[4].links.insert(Direction::West, 3);
|
|
|
|
Keypad { buttons }
|
|
}
|
|
|
|
fn direction_to_char(direction: &Direction) -> char {
|
|
match direction {
|
|
Direction::North => '^',
|
|
Direction::South => 'v',
|
|
Direction::West => '<',
|
|
Direction::East => '>',
|
|
}
|
|
}
|