pathfinding/utils.rs
1//! Miscellaneous utilities
2
3use integer_sqrt::IntegerSquareRoot;
4use num_traits::{PrimInt, Unsigned};
5
6/// Return the square root of `n` if `n` is square, `None` otherwise.
7///
8/// # Example
9///
10/// ```
11/// use pathfinding::utils::uint_sqrt;
12///
13/// assert_eq!(uint_sqrt(100usize), Some(10));
14/// assert_eq!(uint_sqrt(10usize), None);
15/// ```
16pub fn uint_sqrt<T>(n: T) -> Option<T>
17where
18 T: PrimInt + Unsigned,
19{
20 let root = n.integer_sqrt();
21 (n == root * root).then_some(root)
22}
23
24/// Move a two-dimensional coordinate into a given direction provided that:
25/// - The `start` point is valid (given the `dimensions`).
26/// - The `direction` is not `(0,0)`
27/// - The target point is valid (given the `dimensions`).
28///
29/// # Example
30///
31/// ```
32/// use pathfinding::utils::move_in_direction;
33///
34/// let board = (8, 8);
35/// assert_eq!(move_in_direction((5, 5), (-1, -2), board), Some((4, 3)));
36/// assert_eq!(move_in_direction((1, 1), (-1, -2), board), None);
37/// ```
38#[must_use]
39#[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)]
40pub fn move_in_direction(
41 start: (usize, usize),
42 direction: (isize, isize),
43 dimensions: (usize, usize),
44) -> Option<(usize, usize)> {
45 let (row, col) = start;
46 if row >= dimensions.0 || col >= dimensions.1 || direction == (0, 0) {
47 return None;
48 }
49 let (new_row, new_col) = (row as isize + direction.0, col as isize + direction.1);
50 (new_row >= 0
51 && (new_row as usize) < dimensions.0
52 && new_col >= 0
53 && (new_col as usize) < dimensions.1)
54 .then_some((new_row as usize, new_col as usize))
55}
56
57/// Repeatedly call [`move_in_direction`] until the returned value
58/// is `None`.
59///
60/// # Example
61///
62/// ```
63/// use pathfinding::utils::in_direction;
64///
65/// let board = (8, 8);
66/// let positions = in_direction((0, 0), (1, 2), board).collect::<Vec<_>>();
67/// assert_eq!(positions, vec![(1, 2), (2, 4), (3, 6)]);
68/// ```
69pub fn in_direction(
70 start: (usize, usize),
71 direction: (isize, isize),
72 dimensions: (usize, usize),
73) -> impl Iterator<Item = (usize, usize)> {
74 std::iter::successors(Some(start), move |current| {
75 move_in_direction(*current, direction, dimensions)
76 })
77 .skip(1)
78}
79
80/// Constrain `value` into `0..upper` by adding or subtracting `upper`
81/// as many times as necessary.
82///
83/// # Examples
84///
85/// ```
86/// use pathfinding::utils::constrain;
87///
88/// assert_eq!(constrain(5, 7), 5);
89/// assert_eq!(constrain(30, 7), 2);
90/// assert_eq!(constrain(-30, 7), 5);
91/// ```
92#[must_use]
93#[allow(clippy::cast_sign_loss)]
94pub const fn constrain(value: isize, upper: usize) -> usize {
95 if value > 0 {
96 value as usize % upper
97 } else {
98 (upper - (-value) as usize % upper) % upper
99 }
100}