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}