grid_search/
lib.rs

1use std::ops::Range;
2
3/// This `struct` is created by the [`around`] methods.
4/// See its documentation for more.
5///
6/// [`around`]: fn.around.html
7pub struct Around<'a> {
8    y: usize,
9    x: usize,
10    y_range: Range<usize>,
11    x_range: Range<usize>,
12    directions: &'a [(isize, isize)],
13}
14
15/// `(y, x)` を基点とした周辺座標を yield するイテレータを作ります。
16///
17/// # Examples
18/// 隣接 4 方向を走査する例です。
19///
20/// ```
21/// use grid_search::around;
22/// const NSEW: [(isize, isize); 4] = [(-1, 0), (1, 0), (0, 1), (0, -1)];
23/// let mut a = around(0, 1).y_range(0..3).x_range(0..4).directions(&NSEW);
24/// assert_eq!(a.next(), Some((1, 1)));
25/// assert_eq!(a.next(), Some((0, 2)));
26/// assert_eq!(a.next(), Some((0, 0)));
27/// assert_eq!(a.next(), None);
28/// // .x..
29/// // ....
30/// // ....
31/// //
32/// //  |
33/// //  v
34/// //
35/// // x.x.
36/// // .x..
37/// // ....
38/// ```
39pub fn around<'a>(y: usize, x: usize) -> Around<'a> {
40    Around {
41        y,
42        x,
43        y_range: 0..usize::MAX,
44        x_range: 0..usize::MAX,
45        directions: &[],
46    }
47}
48
49impl<'a> Around<'a> {
50    /// 上下方向の範囲をセットします。デフォルトは `0..usize::MAX` です。
51    pub fn y_range(self, r: Range<usize>) -> Self {
52        Self { y_range: r, ..self }
53    }
54    /// 左右方向の範囲をセットします。デフォルトは `0..usize::MAX` です。
55    pub fn x_range(self, r: Range<usize>) -> Self {
56        Self { x_range: r, ..self }
57    }
58    /// 基点からの相対座標たちをセットします。デフォルトは空のスライスです。
59    pub fn directions(self, dirs: &'a [(isize, isize)]) -> Self {
60        Self {
61            directions: dirs,
62            ..self
63        }
64    }
65}
66
67impl<'a> Iterator for Around<'a> {
68    type Item = (usize, usize);
69    fn next(&mut self) -> Option<Self::Item> {
70        while let Some((&(dy, dx), rest)) = self.directions.split_first() {
71            self.directions = rest;
72            match (self.y.checked_add_signed(dy), self.x.checked_add_signed(dx)) {
73                (Some(ny), Some(nx))
74                    if self.y_range.contains(&self.y)
75                        && self.x_range.contains(&self.x)
76                        && self.y_range.contains(&ny)
77                        && self.x_range.contains(&nx) =>
78                {
79                    return Some((ny, nx));
80                }
81                _ => {}
82            }
83        }
84        None
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91    const NSEW: [(isize, isize); 4] = [(-1, 0), (1, 0), (0, 1), (0, -1)];
92
93    #[test]
94    fn simple() {
95        let mut a = around(5, 5).y_range(0..10).x_range(0..10).directions(&NSEW);
96        assert_eq!(a.next(), Some((4, 5)));
97        assert_eq!(a.next(), Some((6, 5)));
98        assert_eq!(a.next(), Some((5, 6)));
99        assert_eq!(a.next(), Some((5, 4)));
100        assert_eq!(a.next(), None);
101    }
102
103    #[test]
104    fn out_of_bounds_dest() {
105        let mut a = around(1, 1).y_range(1..10).x_range(1..10).directions(&NSEW);
106        assert_eq!(a.next(), Some((2, 1)));
107        assert_eq!(a.next(), Some((1, 2)));
108        assert_eq!(a.next(), None); // != (0, 1), (1, 0)
109    }
110
111    #[test]
112    fn out_of_bounds_source() {
113        let mut a = around(9, 10)
114            .y_range(0..10)
115            .x_range(0..10)
116            .directions(&NSEW);
117        assert_eq!(a.next(), None); // != (9, 9)
118    }
119
120    #[test]
121    fn no_directions() {
122        let mut a = around(5, 5).y_range(0..10).x_range(0..10);
123        assert_eq!(a.next(), None);
124    }
125}