Solve 21
This commit is contained in:
parent
6252e787ad
commit
51a2a71716
1 changed files with 152 additions and 0 deletions
152
src/bin/21.rs
Normal file
152
src/bin/21.rs
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
use aoc::*;
|
||||||
|
use cached::proc_macro::cached;
|
||||||
|
use itertools::Itertools;
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::iter;
|
||||||
|
|
||||||
|
const INPUT: &str = include_str!("../../input/21");
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
assert_example!(part1, "21-test", 126384);
|
||||||
|
println!("Part 1: {}", part1(INPUT));
|
||||||
|
println!("Part 2: {}", part2(INPUT));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn part1(input: &str) -> usize {
|
||||||
|
let pads = vec![Pad::Num, Pad::Dir, Pad::Dir];
|
||||||
|
input
|
||||||
|
.lines()
|
||||||
|
.map(|code| complexity(code, pads.clone()))
|
||||||
|
.sum()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn part2(input: &str) -> usize {
|
||||||
|
let robots = iter::repeat(Pad::Dir).take(25);
|
||||||
|
let pads = iter::once(Pad::Num).chain(robots).collect_vec();
|
||||||
|
input
|
||||||
|
.lines()
|
||||||
|
.map(|code| complexity(code, pads.clone()))
|
||||||
|
.sum()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn complexity(code: &str, pads: Vec<Pad>) -> usize {
|
||||||
|
let sequence = expand(code.to_string(), pads);
|
||||||
|
let num: usize = code[0..3].parse().unwrap();
|
||||||
|
sequence * num
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all shortest possible expansions.
|
||||||
|
#[cached]
|
||||||
|
fn expand(input: String, mut pads: Vec<Pad>) -> usize {
|
||||||
|
let pad = if pads.is_empty() {
|
||||||
|
return input.len();
|
||||||
|
} else {
|
||||||
|
pads.remove(0)
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut output = 0;
|
||||||
|
let mut current = 'A';
|
||||||
|
|
||||||
|
for target in input.chars() {
|
||||||
|
let expansion = shortest_paths(current, target, pad)
|
||||||
|
.into_iter()
|
||||||
|
.map(|path| {
|
||||||
|
let mut required_moves: String = path.into_iter().map(|(_, dir)| dir).collect();
|
||||||
|
required_moves.push('A');
|
||||||
|
expand(required_moves, pads.clone())
|
||||||
|
})
|
||||||
|
.min()
|
||||||
|
.unwrap();
|
||||||
|
output += expansion;
|
||||||
|
current = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cached]
|
||||||
|
fn shortest_paths(start: char, end: char, pad: Pad) -> Vec<Vec<(char, char)>> {
|
||||||
|
if start == end {
|
||||||
|
return vec![vec![]];
|
||||||
|
}
|
||||||
|
|
||||||
|
let next_chars = pad.next_moves();
|
||||||
|
|
||||||
|
let initial_work = next_chars(start).iter().map(|&w| vec![w]);
|
||||||
|
let mut work: VecDeque<Vec<(char, char)>> = VecDeque::from_iter(initial_work);
|
||||||
|
|
||||||
|
let mut shortest_paths = Vec::new();
|
||||||
|
let mut shortest_length = usize::MAX;
|
||||||
|
|
||||||
|
while let Some(path) = work.pop_front() {
|
||||||
|
if path.len() > shortest_length {
|
||||||
|
// BFS can only get worse now
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let head = path.last().copied().unwrap();
|
||||||
|
|
||||||
|
if head.0 == end {
|
||||||
|
shortest_length = path.len();
|
||||||
|
shortest_paths.push(path);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for &next in next_chars(head.0) {
|
||||||
|
if path.iter().any(|&(c, _)| c == next.0) {
|
||||||
|
// Already been there
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let mut path = path.clone();
|
||||||
|
path.push(next);
|
||||||
|
work.push_back(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
shortest_paths
|
||||||
|
}
|
||||||
|
|
||||||
|
type Moves = &'static [(char, char)];
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
enum Pad {
|
||||||
|
Num,
|
||||||
|
Dir,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pad {
|
||||||
|
fn next_moves(self) -> fn(char) -> Moves {
|
||||||
|
match self {
|
||||||
|
Self::Num => numpad_next,
|
||||||
|
Self::Dir => dirpad_next,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn numpad_next(current: char) -> Moves {
|
||||||
|
match current {
|
||||||
|
'A' => &[('0', '<'), ('3', '^')],
|
||||||
|
'0' => &[('A', '>'), ('2', '^')],
|
||||||
|
'1' => &[('2', '>'), ('4', '^')],
|
||||||
|
'2' => &[('0', 'v'), ('1', '<'), ('3', '>'), ('5', '^')],
|
||||||
|
'3' => &[('A', 'v'), ('2', '<'), ('6', '^')],
|
||||||
|
'4' => &[('1', 'v'), ('5', '>'), ('7', '^')],
|
||||||
|
'5' => &[('2', 'v'), ('4', '<'), ('6', '>'), ('8', '^')],
|
||||||
|
'6' => &[('3', 'v'), ('5', '<'), ('9', '^')],
|
||||||
|
'7' => &[('4', 'v'), ('8', '>')],
|
||||||
|
'8' => &[('5', 'v'), ('7', '<'), ('9', '>')],
|
||||||
|
'9' => &[('6', 'v'), ('8', '<')],
|
||||||
|
other => unreachable!("unknown numpad char: '{other}'"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dirpad_next(current: char) -> Moves {
|
||||||
|
match current {
|
||||||
|
'A' => &[('>', 'v'), ('^', '<')],
|
||||||
|
'^' => &[('v', 'v'), ('A', '>')],
|
||||||
|
'v' => &[('<', '<'), ('>', '>'), ('^', '^')],
|
||||||
|
'<' => &[('v', '>')],
|
||||||
|
'>' => &[('v', '<'), ('A', '^')],
|
||||||
|
other => unreachable!("unknown dirpad char: '{other}'"),
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue