use crate::util::hash::*;
use crate::util::parse::*;
use crate::util::point::*;
use std::iter::{once, repeat_n};
type Input = (Vec<(String, usize)>, Combinations);
type Combinations = FastMap<(char, char), Vec<String>>;
type Cache = FastMap<(char, char, usize), usize>;
pub fn parse(input: &str) -> Input {
let pairs = input.lines().map(String::from).zip(input.iter_unsigned()).collect();
(pairs, pad_combinations())
}
pub fn part1(input: &Input) -> usize {
chain(input, 3)
}
pub fn part2(input: &Input) -> usize {
chain(input, 26)
}
fn chain(input: &Input, depth: usize) -> usize {
let (pairs, combinations) = input;
let cache = &mut FastMap::with_capacity(500);
pairs.iter().map(|(code, numeric)| dfs(cache, combinations, code, depth) * numeric).sum()
}
fn dfs(cache: &mut Cache, combinations: &Combinations, code: &str, depth: usize) -> usize {
if depth == 0 {
return code.len();
}
let mut previous = 'A';
let mut result = 0;
for current in code.chars() {
let key = (previous, current, depth);
result += cache.get(&key).copied().unwrap_or_else(|| {
let presses = combinations[&(previous, current)]
.iter()
.map(|next| dfs(cache, combinations, next, depth - 1))
.min()
.unwrap();
cache.insert(key, presses);
presses
});
previous = current;
}
result
}
fn pad_combinations() -> Combinations {
let numeric_gap = Point::new(0, 3);
let numeric_keys = [
('7', Point::new(0, 0)),
('8', Point::new(1, 0)),
('9', Point::new(2, 0)),
('4', Point::new(0, 1)),
('5', Point::new(1, 1)),
('6', Point::new(2, 1)),
('1', Point::new(0, 2)),
('2', Point::new(1, 2)),
('3', Point::new(2, 2)),
('0', Point::new(1, 3)),
('A', Point::new(2, 3)),
];
let directional_gap = Point::new(0, 0);
let directional_keys = [
('^', Point::new(1, 0)),
('A', Point::new(2, 0)),
('<', Point::new(0, 1)),
('v', Point::new(1, 1)),
('>', Point::new(2, 1)),
];
let mut combinations = FastMap::with_capacity(145);
pad_routes(&mut combinations, &numeric_keys, numeric_gap);
pad_routes(&mut combinations, &directional_keys, directional_gap);
combinations
}
fn pad_routes(combinations: &mut Combinations, pad: &[(char, Point)], gap: Point) {
for &(first, from) in pad {
for &(second, to) in pad {
let horizontal = || {
let element = if from.x < to.x { '>' } else { '<' };
let count = from.x.abs_diff(to.x) as usize;
repeat_n(element, count)
};
let vertical = || {
let element = if from.y < to.y { 'v' } else { '^' };
let count = from.y.abs_diff(to.y) as usize;
repeat_n(element, count)
};
if Point::new(from.x, to.y) != gap {
let path = vertical().chain(horizontal()).chain(once('A')).collect();
combinations.entry((first, second)).or_default().push(path);
}
if from.x != to.x && from.y != to.y && Point::new(to.x, from.y) != gap {
let path = horizontal().chain(vertical()).chain(once('A')).collect();
combinations.entry((first, second)).or_default().push(path);
}
}
}
}