use crate::input::Input;
use std::collections::HashMap;
fn parse(data: &str, programs: &[u8]) -> Result<(Vec<u8>, HashMap<u8, u8>), String> {
let mut moves: Vec<u8> = (0..programs.len()).map(|u| u as u8).collect();
let mut substitutions: HashMap<u8, u8> = programs.iter().map(|&c| (c, c)).collect();
for dance_move in data.split(',') {
if dance_move.is_empty() {
return Err("Empty move".to_string());
}
let args = &mut dance_move[1..].split('/');
if dance_move.starts_with('s') {
let arg_1 = usize::from(
args.next()
.ok_or("Spin not followed by argument")?
.parse::<u8>()
.map_err(|e| format!("Unable to parse Spin argument: {e}"))?,
);
if arg_1 > moves.len() {
return Err(format!(
"Too big spin amount {} for {} moves",
arg_1,
moves.len()
));
}
moves.rotate_right(arg_1);
} else if dance_move.starts_with('x') {
let arg_1 = args
.next()
.ok_or_else(|| "Exchange not followed by two arguments".to_string())?
.parse::<u8>()
.map_err(|e| format!("Unable to parse Exchange argument: {e}"))?;
let arg_2 = args
.next()
.ok_or_else(|| "Exchange not followed by two arguments".to_string())?
.parse::<u8>()
.map_err(|e| format!("Unable to parse Exchange argument: {e}"))?;
moves.swap(arg_1 as usize, arg_2 as usize);
} else if dance_move.starts_with('p') {
let arg_1 = args
.next()
.ok_or_else(|| "Partner not followed by two arguments".to_string())?
.bytes()
.next()
.ok_or_else(|| "Empty Partner argument".to_string())?;
let arg_2 = args
.next()
.ok_or_else(|| "Partner not followed by two arguments".to_string())?
.bytes()
.next()
.ok_or_else(|| "Empty Partner argument".to_string())?;
for (_key, value) in substitutions.iter_mut() {
if *value == arg_1 {
*value = arg_2;
} else if *value == arg_2 {
*value = arg_1;
}
}
} else {
return Err("Invalid dance move not starting with s, x or p".to_string());
}
}
Ok((moves, substitutions))
}
pub fn solve(input: &Input) -> Result<String, String> {
let mut programs = (b'a'..=b'p').collect::<Vec<u8>>();
let mut rounds = input.part_values(1, 1_000_000_000);
let (mut moves, mut substitutions) = parse(input.text, &programs)?;
if substitutions
.iter()
.any(|(_key, value)| !substitutions.contains_key(value))
{
return Err("Invalid input".into());
}
while rounds > 0 {
if rounds % 2 == 1 {
programs = moves
.iter()
.map(|&i| substitutions.get(&programs[i as usize]).cloned())
.collect::<Option<Vec<u8>>>()
.ok_or_else(|| "Internal error".to_string())?;
}
moves = moves.iter().map(|&i| moves[i as usize]).collect();
substitutions = substitutions
.iter()
.map(|(&key, value)| (key, substitutions[value]))
.collect();
rounds /= 2;
}
Ok(programs.iter().map(|b| *b as char).collect())
}
#[test]
pub fn tests() {
use crate::input::{test_part_one, test_part_two};
let real_input = include_str!("day16_input.txt");
test_part_one!(real_input => "iabmedjhclofgknp".to_string());
test_part_two!(real_input => "oildcmfeajhbpngk".to_string());
}