1use lazy_static::lazy_static;
2use std::{convert::TryFrom, fmt, iter::FromIterator, str::FromStr};
3use tinyvec::ArrayVec;
4
5use super::Direction;
6
7lazy_static! {
8 static ref PATH_SPARSE_LOOKUP: Vec<ArrayVec<[Direction; 4]>> = Path::create_sparse_path_lookup();
9}
10
11type Directions = u8;
12
13#[derive(Hash, Clone, Copy, Debug, Default, Eq, PartialEq)]
14pub struct Path(Directions);
15
16impl fmt::Display for Path {
17 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
18 for dir in self.iter() {
19 write!(f, "{}", dir)?
20 }
21
22 Ok(())
23 }
24}
25
26#[rustfmt::skip]
27impl Path {
28 pub fn invert(&self) -> Self {
29 self.iter().map(|d| d.invert()).collect::<Path>()
30 }
31
32 pub fn vertical_symmetry(&self) -> Self {
33 self.iter().map(|d| d.vertical_symmetry()).collect::<Path>()
34 }
35
36 pub fn iter(&self) -> impl Iterator<Item=Direction> {
37 self.into_iter()
38 }
39
40 pub fn len(&self) -> usize {
41 self.iter().count()
42 }
43
44 pub fn is_empty(&self) -> bool {
45 false
46 }
47
48 pub fn as_u8(&self) -> u8 {
49 self.0
50 }
51
52 fn get_direction_at_index(path: u8, index: u8) -> u8 {
53 (path >> (index * 2)) & DIRECTION_MASK
54 }
55
56 fn get_direction_for_dir_bits(bits: u8) -> Direction {
57 DIRECTION_BY_INDEX[bits as usize]
58 }
59
60 fn create_sparse_path_lookup() -> Vec<ArrayVec<[Direction; 4]>> {
61 let mut lookup = Vec::with_capacity(256);
62
63 for i in 0..=255 {
64 let path = Path(i).path_into_array_vec_no_lookup_slow();
65 lookup.push(path);
66 }
67
68 lookup
69 }
70
71 fn path_into_array_vec_no_lookup_slow(&self) -> ArrayVec<[Direction; 4]> {
72 let mut directions = ArrayVec::new();
73 let mut prev_dir = 0;
74
75 for i in 0..4 {
76 let curr_dir = Path::get_direction_at_index(self.0, i);
77
78 if i != 0 && curr_dir ^ prev_dir == 0b11 {
79 break;
80 }
81
82 directions.push(Path::get_direction_for_dir_bits(curr_dir));
83 prev_dir = curr_dir;
84 }
85
86 directions
87 }
88}
89
90impl FromStr for Path {
91 type Err = anyhow::Error;
92
93 fn from_str(s: &str) -> Result<Self, Self::Err> {
94 s.chars().map(Direction::try_from).collect::<Result<Path, Self::Err>>()
95 }
96}
97
98impl FromIterator<Direction> for Path {
99 fn from_iter<I: IntoIterator<Item = Direction>>(iter: I) -> Self {
100 let bits_for_dir = |dir: Direction| match dir {
101 Direction::Up => 0b00,
102 Direction::Right => 0b01,
103 Direction::Left => 0b10,
104 Direction::Down => 0b11,
105 };
106
107 let (last_dir_bit, mut path_bits, last_i) = iter.into_iter().enumerate().fold((0u8, 0u8, 0usize), |(_, mut path_bits, _), (i, dir)| {
108 let dir_bits = bits_for_dir(dir);
109
110 path_bits |= dir_bits << (i * 2);
111
112 (dir_bits, path_bits, i)
113 });
114
115 if last_i < 3 {
116 let opposite_dir = !last_dir_bit & DIRECTION_MASK;
117 path_bits |= opposite_dir << ((last_i + 1) * 2);
118 }
119
120 Path(path_bits)
121 }
122}
123
124impl<'a> FromIterator<&'a Direction> for Path {
125 fn from_iter<I: IntoIterator<Item = &'a Direction>>(iter: I) -> Self {
126 iter.into_iter().copied().collect::<Path>()
127 }
128}
129
130const DIRECTION_MASK: u8 = 0b11;
132const DIRECTION_BY_INDEX: [Direction; 4] = [
133 Direction::Up, Direction::Right, Direction::Left, Direction::Down, ];
138
139impl IntoIterator for Path {
140 type Item = Direction;
141 type IntoIter = tinyvec::ArrayVecIterator<[Direction; 4]>;
142
143 fn into_iter(self) -> Self::IntoIter {
144 (&self).into_iter()
145 }
146}
147
148impl IntoIterator for &Path {
149 type Item = Direction;
150 type IntoIter = tinyvec::ArrayVecIterator<[Direction; 4]>;
151
152 fn into_iter(self) -> Self::IntoIter {
153 PATH_SPARSE_LOOKUP[self.0 as usize].into_iter()
154 }
155}
156
157impl From<&[Direction]> for Path {
158 fn from(value: &[Direction]) -> Self {
159 value.iter().collect::<Path>()
160 }
161}