use lazy_static::lazy_static;
use std::{convert::TryFrom, fmt, iter::FromIterator, str::FromStr};
use tinyvec::ArrayVec;
use super::Direction;
lazy_static! {
static ref PATH_SPARSE_LOOKUP: Vec<ArrayVec<[Direction; 4]>> = Path::create_sparse_path_lookup();
}
type Directions = u8;
#[derive(Hash, Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct Path(Directions);
impl fmt::Display for Path {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for dir in self.iter() {
write!(f, "{}", dir)?
}
Ok(())
}
}
#[rustfmt::skip]
impl Path {
pub fn invert(&self) -> Self {
self.iter().map(|d| d.invert()).collect::<Path>()
}
pub fn vertical_symmetry(&self) -> Self {
self.iter().map(|d| d.vertical_symmetry()).collect::<Path>()
}
pub fn iter(&self) -> impl Iterator<Item=Direction> {
self.into_iter()
}
pub fn len(&self) -> usize {
self.iter().count()
}
pub fn is_empty(&self) -> bool {
false
}
pub fn as_u8(&self) -> u8 {
self.0
}
fn get_direction_at_index(path: u8, index: u8) -> u8 {
(path >> (index * 2)) & DIRECTION_MASK
}
fn get_direction_for_dir_bits(bits: u8) -> Direction {
DIRECTION_BY_INDEX[bits as usize]
}
fn create_sparse_path_lookup() -> Vec<ArrayVec<[Direction; 4]>> {
let mut lookup = Vec::with_capacity(256);
for i in 0..=255 {
let path = Path(i).path_into_array_vec_no_lookup_slow();
lookup.push(path);
}
lookup
}
fn path_into_array_vec_no_lookup_slow(&self) -> ArrayVec<[Direction; 4]> {
let mut directions = ArrayVec::new();
let mut prev_dir = 0;
for i in 0..4 {
let curr_dir = Path::get_direction_at_index(self.0, i);
if i != 0 && curr_dir ^ prev_dir == 0b11 {
break;
}
directions.push(Path::get_direction_for_dir_bits(curr_dir));
prev_dir = curr_dir;
}
directions
}
}
impl FromStr for Path {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.chars().map(Direction::try_from).collect::<Result<Path, Self::Err>>()
}
}
impl FromIterator<Direction> for Path {
fn from_iter<I: IntoIterator<Item = Direction>>(iter: I) -> Self {
let bits_for_dir = |dir: Direction| match dir {
Direction::Up => 0b00,
Direction::Right => 0b01,
Direction::Left => 0b10,
Direction::Down => 0b11,
};
let (last_dir_bit, mut path_bits, last_i) = iter.into_iter().enumerate().fold((0u8, 0u8, 0usize), |(_, mut path_bits, _), (i, dir)| {
let dir_bits = bits_for_dir(dir);
path_bits |= dir_bits << (i * 2);
(dir_bits, path_bits, i)
});
if last_i < 3 {
let opposite_dir = !last_dir_bit & DIRECTION_MASK;
path_bits |= opposite_dir << ((last_i + 1) * 2);
}
Path(path_bits)
}
}
impl<'a> FromIterator<&'a Direction> for Path {
fn from_iter<I: IntoIterator<Item = &'a Direction>>(iter: I) -> Self {
iter.into_iter().copied().collect::<Path>()
}
}
const DIRECTION_MASK: u8 = 0b11;
const DIRECTION_BY_INDEX: [Direction; 4] = [
Direction::Up, Direction::Right, Direction::Left, Direction::Down, ];
impl IntoIterator for Path {
type Item = Direction;
type IntoIter = tinyvec::ArrayVecIterator<[Direction; 4]>;
fn into_iter(self) -> Self::IntoIter {
(&self).into_iter()
}
}
impl IntoIterator for &Path {
type Item = Direction;
type IntoIter = tinyvec::ArrayVecIterator<[Direction; 4]>;
fn into_iter(self) -> Self::IntoIter {
PATH_SPARSE_LOOKUP[self.0 as usize].into_iter()
}
}
impl From<&[Direction]> for Path {
fn from(value: &[Direction]) -> Self {
value.iter().collect::<Path>()
}
}