use itertools::Itertools;
use lazy_static::lazy_static;
use std::convert::TryFrom;
use std::iter::repeat_with;
use tinyvec::*;
use super::{Direction, MoveDirection, Path};
pub type PathPermutations = ArrayVec<Paths>;
type Paths = [(ArrayVec<[Direction; 4]>, Path); 6];
type DirectionsAndPaths = Vec<DirectionsAndPath>;
type DirectionsAndPath = (MoveDirection, PathPermutations);
lazy_static! {
static ref PATH_PERMUTATIONS_1: Vec<PathPermutations> = get_path_permutations_inner(1).into_iter().map(|(_, path)| path).collect();
static ref PATH_PERMUTATIONS_2: Vec<PathPermutations> = get_path_permutations_inner(2).into_iter().map(|(_, path)| path).collect();
static ref PATH_PERMUTATIONS_3: Vec<PathPermutations> = get_path_permutations_inner(3).into_iter().map(|(_, path)| path).collect();
static ref PATH_PERMUTATIONS_4: Vec<PathPermutations> = get_path_permutations_inner(4).into_iter().map(|(_, path)| path).collect();
static ref PATH_PERMUTATIONS_EXACT_STEPS_1: Vec<PathPermutations> = get_path_permutations_with_exact_steps_inner(1);
static ref PATH_PERMUTATIONS_EXACT_STEPS_2: Vec<PathPermutations> = get_path_permutations_with_exact_steps_inner(2);
static ref PATH_PERMUTATIONS_EXACT_STEPS_3: Vec<PathPermutations> = get_path_permutations_with_exact_steps_inner(3);
static ref PATH_PERMUTATIONS_EXACT_STEPS_4: Vec<PathPermutations> = get_path_permutations_with_exact_steps_inner(4);
static ref PATH_PERMUTATIONS_BY_MOVE_DIRECTION: Vec<(MoveDirection, ArrayVec<[Path; 6]>)> = get_path_permutations_by_move_direction_inner();
}
pub fn get_path_permutations(num_steps: usize) -> &'static [PathPermutations] {
match num_steps {
1 => &PATH_PERMUTATIONS_1,
2 => &PATH_PERMUTATIONS_2,
3 => &PATH_PERMUTATIONS_3,
4 => &PATH_PERMUTATIONS_4,
_ => panic!("No matching move direction for {} steps.", num_steps),
}
}
pub fn get_path_permutations_with_exact_steps(exact_num_steps: usize) -> &'static [PathPermutations] {
match exact_num_steps {
1 => &PATH_PERMUTATIONS_EXACT_STEPS_1,
2 => &PATH_PERMUTATIONS_EXACT_STEPS_2,
3 => &PATH_PERMUTATIONS_EXACT_STEPS_3,
4 => &PATH_PERMUTATIONS_EXACT_STEPS_4,
_ => panic!("No matching move direction for {} steps.", exact_num_steps),
}
}
pub fn get_path_permutations_by_move_direction() -> &'static [(MoveDirection, ArrayVec<[Path; 6]>)] {
&PATH_PERMUTATIONS_BY_MOVE_DIRECTION
}
fn get_path_permutations_by_move_direction_inner() -> Vec<(MoveDirection, ArrayVec<[Path; 6]>)> {
let perms = get_path_permutations_inner(4);
MoveDirection::move_directions()
.into_iter()
.map(|dir| perms.iter().find(|(d, _a)| dir == *d).expect("Should always contain a matching move direction"))
.map(|(dir, a)| (*dir, a.iter().map(|(_, path)| *path).collect::<ArrayVec<_>>()))
.collect()
}
fn get_path_permutations_with_exact_steps_inner(exact_num_steps: usize) -> Vec<PathPermutations> {
get_path_permutations(exact_num_steps)
.iter()
.map(|moves| moves.iter().filter(|(steps, _)| steps.len() == exact_num_steps).cloned().collect())
.filter(|moves: &PathPermutations| !moves.is_empty())
.collect()
}
fn get_path_permutations_inner(num_steps: usize) -> DirectionsAndPaths {
let dirs = [Direction::Up, Direction::Right, Direction::Down, Direction::Left];
let prods = |n: usize| repeat_with(|| dirs.iter().cloned()).take(n).multi_cartesian_product();
let dir_products = (1..=num_steps).flat_map(prods).filter(|p| !has_redundant_directions(p.as_slice()));
let pairs = dir_products.map(|dirs| (MoveDirection::try_from(dirs.as_slice()).unwrap(), dirs));
pairs.fold(Vec::<_>::new(), |mut map, (k, dirs)| {
let entry = map.iter_mut().find(|(key, _)| *key == k);
if entry.is_none() {
map.push((k, ArrayVec::new()));
}
let entry = map.iter_mut().find(|(key, _)| *key == k).unwrap();
entry.1.push((ArrayVec::try_from(dirs.as_slice()).unwrap(), Path::from(dirs.as_slice())));
map
})
}
fn has_redundant_directions(dirs: &[Direction]) -> bool {
for window in (2..=3).flat_map(|len| dirs.windows(len)) {
if contains_opposite_dirs(window) {
return true;
}
}
false
}
fn contains_opposite_dirs(dirs: &[Direction]) -> bool {
if dirs.contains(&Direction::Up) && dirs.contains(&Direction::Down) {
return true;
}
if dirs.contains(&Direction::Right) && dirs.contains(&Direction::Left) {
return true;
}
false
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_verify_number_of_permutations_by_step() {
let get_sum_of_all_path_permutations = |paths: &[PathPermutations]| -> usize { paths.iter().map(|path| path.len()).sum() };
let perms = get_path_permutations(1);
assert_eq!(perms.len(), 4);
assert_eq!(get_sum_of_all_path_permutations(perms), 4);
let perms = get_path_permutations(2);
assert_eq!(perms.len(), 12);
assert_eq!(get_sum_of_all_path_permutations(perms), 16);
let perms = get_path_permutations(3);
assert_eq!(perms.len(), 24);
assert_eq!(get_sum_of_all_path_permutations(perms), 44);
let perms = get_path_permutations(4);
assert_eq!(perms.len(), 40);
assert_eq!(get_sum_of_all_path_permutations(perms), 112);
}
#[test]
fn test_verify_number_of_permutations_exact_step() {
let get_sum_of_all_path_permutations = |paths: &[PathPermutations]| -> usize { paths.iter().map(|path| path.len()).sum() };
let perms = get_path_permutations_with_exact_steps(1);
assert_eq!(perms.len(), 4);
assert_eq!(get_sum_of_all_path_permutations(perms), 4);
let perms = get_path_permutations_with_exact_steps(2);
assert_eq!(perms.len(), 8);
assert_eq!(get_sum_of_all_path_permutations(perms), 12);
let perms = get_path_permutations_with_exact_steps(3);
assert_eq!(perms.len(), 12);
assert_eq!(get_sum_of_all_path_permutations(perms), 28);
let perms = get_path_permutations_with_exact_steps(4);
assert_eq!(perms.len(), 20);
assert_eq!(get_sum_of_all_path_permutations(perms), 68);
}
}