balanced_direction/path.rs
1use crate::Balance;
2use alloc::vec::Vec;
3
4/// Represents a sequence of movements in a grid, where each movement
5/// is represented by a `Balance` value indicating the direction of one step.
6///
7/// The `Path` struct provides various utilities to manipulate and analyze the sequence
8/// of movements, including iteration, transformation, normalization, and reversal.
9///
10/// # Examples
11///
12/// Creating a new `Path`:
13/// ```
14/// use balanced_direction::{Balance, Path};
15///
16/// let movements = vec![Balance::Top, Balance::Right, Balance::Bottom];
17/// let path = Path::new(movements);
18/// assert_eq!(path.len(), 3);
19/// ```
20///
21/// Normalizing a `Path`:
22/// ```
23/// use balanced_direction::{Balance, Path};
24///
25/// let movements = vec![Balance::Top, Balance::Top];
26/// let path = Path::new(movements).normalized();
27/// assert_eq!(path.to_vector(), (0, -2));
28/// ```
29///
30/// Reversing a `Path`:
31/// ```
32/// use balanced_direction::{Balance, Path};
33///
34/// let movements = vec![Balance::Top, Balance::Right];
35/// let path = Path::new(movements).reversed();
36/// assert_eq!(path.to_vector(), (1, -1));
37/// ```
38#[derive(Clone, Debug, PartialEq)]
39pub struct Path {
40 raw: Vec<Balance>,
41}
42
43impl Path {
44 /// Creates a new `Path` from a vector of movements.
45 ///
46 /// Each movement in the vector represents a step in a 3x3 grid,
47 /// where each step is a `Balance` value indicating direction or position.
48 ///
49 /// # Arguments
50 ///
51 /// * `movements` - A `Vec` of `Balance` values representing the sequence of movements.
52 ///
53 /// # Returns
54 ///
55 /// A new `Path` instance containing the provided sequence of movements.
56 ///
57 /// # Examples
58 ///
59 /// ```
60 /// use balanced_direction::{Balance, Path};
61 ///
62 /// let movements = vec![Balance::Top, Balance::Right, Balance::Bottom];
63 /// let path = Path::new(movements);
64 /// assert_eq!(path.len(), 3);
65 /// ```
66 pub fn new(movements: Vec<Balance>) -> Self {
67 Self { raw: movements }
68 }
69
70 /// Returns the number of movements in the `Path`.
71 ///
72 /// # Returns
73 ///
74 /// An integer representing the number of elements in the `Path`.
75 pub fn len(&self) -> usize {
76 self.raw.len()
77 }
78
79 /// Checks whether the `Path` is empty.
80 ///
81 /// # Returns
82 ///
83 /// `true` if the `Path` contains no movements, `false` otherwise.
84 pub fn is_empty(&self) -> bool {
85 self.raw.is_empty()
86 }
87
88 /// Retrieves the `Balance` at the specified index in the `Path`.
89 ///
90 /// # Arguments
91 ///
92 /// * `index` - The position of the `Balance` in the `Path` to retrieve.
93 ///
94 /// # Returns
95 ///
96 /// An `Option` containing a reference to the `Balance` if the index is valid, or `None` otherwise.
97 pub fn get(&self, index: usize) -> Option<&Balance> {
98 self.raw.get(index)
99 }
100
101 /// Returns an iterator over immutable references to the `Balance` values in the `Path`.
102 ///
103 /// The iterator allows traversing the sequence of movements without modifying it.
104 pub fn iter(&self) -> impl Iterator<Item = &Balance> {
105 self.raw.iter()
106 }
107
108 /// Returns an iterator over mutable references to the `Balance` values in the `Path`.
109 ///
110 /// The iterator allows traversing the sequence of movements modifying it.
111 pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Balance> {
112 self.raw.iter_mut()
113 }
114
115 /// Appends a new movement to the end of the `Path`.
116 ///
117 /// # Arguments
118 ///
119 /// * `movement` - The `Balance` value to be added to the `Path`.
120 pub fn push(&mut self, movement: Balance) {
121 self.raw.push(movement);
122 }
123
124 /// Removes the last movement from the `Path`, if any, and returns it.
125 ///
126 /// # Returns
127 ///
128 /// An `Option` containing the `Balance` value that was removed, or `None` if the `Path` is empty.
129 pub fn pop(&mut self) -> Option<Balance> {
130 self.raw.pop()
131 }
132
133 /// Clears all movements from the `Path`, leaving it empty.
134 pub fn clear(&mut self) {
135 self.raw.clear();
136 }
137
138 /// Converts the sequence of movements in the `Path` to a vector representation.
139 ///
140 /// Each `Balance` value in the `Path` contributes a two-dimensional `(i8, i8)` vector,
141 /// which represents its direction or position in the grid. The resulting vector
142 /// is the cumulative sum of all movements in the sequence.
143 ///
144 /// # Returns
145 ///
146 /// A tuple `(i8, i8)` where:
147 /// - The first element is the cumulative movement along the x-axis.
148 /// - The second element is the cumulative movement along the y-axis.
149 ///
150 /// # Examples
151 ///
152 /// ```
153 /// use balanced_direction::{Balance, Path};
154 ///
155 /// let movements = vec![Balance::Top, Balance::Right, Balance::Top];
156 /// let path = Path::new(movements);
157 /// let vector = path.to_vector();
158 /// assert_eq!(vector, (1, -2)); // 1 step right, 2 steps up
159 /// ```
160 pub fn to_vector(&self) -> (i8, i8) {
161 let mut x = 0;
162 let mut y = 0;
163 for movement in self.raw.iter() {
164 let (a, b) = movement.to_vector();
165 x += a;
166 y += b;
167 }
168 (x, y)
169 }
170
171 /// Converts a vector representation `(x, y)` into a `Path`.
172 ///
173 /// This function takes two integers, `x` and `y`, representing cumulative movements along
174 /// the x-axis and y-axis, respectively, in a 2D grid. It decomposes these movements into
175 /// individual steps represented as a sequence of `Balance` values, which are stored in a `Path`.
176 ///
177 /// Movements are calculated progressively by reducing the values of `x` and `y` by their sign
178 /// in each step until both reach 0. Each step corresponds to a direction as determined
179 /// by `Balance::from_vector`.
180 ///
181 /// # Arguments
182 ///
183 /// * `x` - An `i8` representing the movement along the x-axis.
184 /// * `y` - An `i8` representing the movement along the y-axis.
185 ///
186 /// # Returns
187 ///
188 /// A `Path` instance containing a sequence of movements that achieve the given `x` and `y` displacements.
189 ///
190 /// # Examples
191 ///
192 /// ```
193 /// use balanced_direction::{Balance, Path};
194 ///
195 /// let path = Path::from_vector(2, -1);
196 /// assert_eq!(path.to_vector(), (2, -1));
197 /// ```
198 pub fn from_vector(x: i8, y: i8) -> Self {
199 let mut movements = Vec::new();
200 let mut x = x;
201 let mut y = y;
202 while x != 0 || y != 0 {
203 let (a, b) = (x.signum(), y.signum());
204 x -= a;
205 y -= b;
206 movements.push(Balance::from_vector(a, b));
207 }
208 Self { raw: movements }
209 }
210
211 /// Returns a normalized `Path`.
212 ///
213 /// The normalized `Path` is constructed by converting the sequence of movements
214 /// in the current `Path` into their cumulative vector representation using `to_vector`
215 /// and then converting this vector back into a `Path` using `from_vector`.
216 ///
217 /// This effectively removes redundant steps in the `Path` that cancel each other out,
218 /// resulting in a minimal representation of the net movement.
219 ///
220 /// # Examples
221 ///
222 /// ```
223 /// use balanced_direction::{Balance, Path};
224 ///
225 /// let movements = vec![Balance::Top, Balance::Bottom, Balance::Right, Balance::Right];
226 /// let path = Path::new(movements);
227 /// let normalized_path = path.normalized();
228 /// assert_eq!(normalized_path.to_vector(), (2, 0)); // Two steps right
229 /// ```
230 pub fn normalized(&self) -> Self {
231 let (x, y) = self.to_vector();
232 Self::from_vector(x, y)
233 }
234
235 /// Reverses the sequence of movements in the `Path`.
236 ///
237 /// The reversed `Path` will have its movements ordered in the opposite direction
238 /// compared to the original `Path`. The order of the movements is inverted,
239 /// but the movements themselves remain unchanged.
240 ///
241 /// # Returns
242 ///
243 /// A new `Path` instance containing the reversed sequence of movements.
244 ///
245 /// # Examples
246 ///
247 /// ```
248 /// use balanced_direction::{Balance, Path};
249 ///
250 /// let movements = vec![Balance::Top, Balance::Right, Balance::Left];
251 /// let path = Path::new(movements);
252 /// let reversed_path = path.reversed();
253 /// assert_eq!(path.to_vector(), (0, -1));
254 /// assert_eq!(reversed_path.to_vector(), (0, -1));
255 /// ```
256 pub fn reversed(&self) -> Self {
257 let mut movements = Vec::new();
258 for movement in self.raw.iter().rev() {
259 movements.push(*movement);
260 }
261 Self { raw: movements }
262 }
263
264 /// Applies a function `f` to each `Balance` in the `Path` and returns a new `Path` containing the results.
265 ///
266 /// This method iterates over all movements in the `Path`, applies the function `f` to each movement,
267 /// and collects the resulting `Balance` values into a new `Path`.
268 ///
269 /// # Arguments
270 ///
271 /// * `f` - A function or closure of type `Fn(Balance) -> Balance` that takes a `Balance` as input
272 /// and returns a transformed `Balance`.
273 ///
274 /// # Returns
275 ///
276 /// A new `Path` where each `Balance` is the result of applying `f` to the corresponding
277 /// `Balance` in the original `Path`.
278 ///
279 /// # Example
280 ///
281 /// ```
282 /// use balanced_direction::{Balance, Path};
283 ///
284 /// let movements = vec![Balance::Top, Balance::Right, Balance::Left];
285 /// let path = Path::new(movements);
286 /// let transformed_path = path.each(Balance::up);
287 /// assert_eq!(
288 /// transformed_path.to_vector(),
289 /// (0, -3)
290 /// );
291 /// ```
292 pub fn each(&self, f: impl Fn(Balance) -> Balance) -> Self {
293 let mut movements = Vec::with_capacity(self.raw.len());
294 for movement in self.raw.iter() {
295 movements.push(f(*movement));
296 }
297 Self { raw: movements }
298 }
299
300 /// Applies a function `f`, which takes two arguments of type `Balance`,
301 /// and returns a transformed `Balance`, to each `Balance` in the current `Path`,
302 /// using the provided `other` value as the second argument.
303 ///
304 /// This method iterates over all movements in the `Path` and applies the function `f`
305 /// to each movement and the `other` value. The results of the function application
306 /// are collected into a new `Path`.
307 ///
308 /// # Arguments
309 ///
310 /// * `f` - A function or closure of type `Fn(Balance, Balance) -> Balance` that
311 /// takes two `Balance` arguments and returns a transformed `Balance`.
312 /// * `other` - A `Balance` value that is passed as the second argument to the function `f`.
313 ///
314 /// # Returns
315 ///
316 /// A new `Path` instance where each `Balance` is the result of applying `f`
317 /// to the corresponding `Balance` in the original `Path` and the `other` value.
318 ///
319 /// # Example
320 ///
321 /// ```
322 /// use std::ops::Add;
323 /// use balanced_direction::{Balance, Path};
324 ///
325 /// let movements = vec![Balance::Left, Balance::TopLeft];
326 /// let path = Path::new(movements);
327 /// let modified_path = path.each_with(Balance::add, Balance::Right);
328 /// assert_eq!(modified_path.to_vector(), (0, -1));
329 /// ```
330 pub fn each_with(&self, f: impl Fn(Balance, Balance) -> Balance, other: Balance) -> Self {
331 let mut movements = Vec::with_capacity(self.raw.len());
332 for movement in self.raw.iter() {
333 movements.push(f(*movement, other));
334 }
335 Self { raw: movements }
336 }
337
338 /// Applies a function `f` to corresponding pairs of `Balance` values from the current `Path`
339 /// and the `other` `Path`, and returns a new `Path` containing the results.
340 ///
341 /// This method iterates over the paired elements of the current `Path` and the provided `other`
342 /// `Path`, applies the function `f` to each pair, and collects the results into a new `Path`.
343 ///
344 /// # Arguments
345 ///
346 /// * `f` - A function or closure of type `Fn(Balance, Balance) -> Balance` that takes two
347 /// `Balance` arguments (one from each `Path`) and returns a transformed `Balance`.
348 /// * `other` - A reference to another `Path` whose `Balance` values will be paired with those of
349 /// the current `Path`.
350 ///
351 /// # Returns
352 ///
353 /// A new `Path` where each `Balance` is the result of applying `f` to corresponding pairs of
354 /// `Balance` values from the current `Path` and the `other` `Path`.
355 ///
356 /// # Panics
357 ///
358 /// Panics if the lengths of the two `Path`s are not equal, as the method expects both `Path`s
359 /// to contain the same number of movements.
360 ///
361 /// # Examples
362 ///
363 /// ```
364 /// use std::ops::Add;
365 /// use balanced_direction::{Balance, Path};
366 ///
367 /// let path1 = Path::new(vec![Balance::Top, Balance::Right]);
368 /// let path2 = Path::new(vec![Balance::Bottom, Balance::Left]);
369 ///
370 /// let result = path1.each_zip(Balance::add, &path2);
371 /// assert_eq!(result.to_vector(), (0, 0));
372 /// ```
373 pub fn each_zip(&self, f: impl Fn(Balance, Balance) -> Balance, other: &Self) -> Self {
374 let mut movements = Vec::with_capacity(self.raw.len());
375 for (a, b) in self.raw.iter().zip(other.raw.iter()) {
376 movements.push(f(*a, *b));
377 }
378 Self { raw: movements }
379 }
380}