all_is_cubes_base/math/
grid_iter.rs

1use core::cmp::Ordering;
2use core::iter::FusedIterator;
3use core::ops::Range;
4
5use crate::math::{Cube, GridAab, GridCoordinate, GridPoint};
6
7/// Iterator produced by [`GridAab::interior_iter()`].
8#[derive(Clone, Debug)]
9pub struct GridIter {
10    x_range: Range<GridCoordinate>,
11    y_range: Range<GridCoordinate>,
12    z_range: Range<GridCoordinate>,
13    cube: GridPoint,
14}
15
16impl GridIter {
17    #[inline]
18    pub(in crate::math) fn new(bounds: GridAab) -> Self {
19        Self {
20            x_range: bounds.x_range(),
21            y_range: bounds.y_range(),
22            z_range: bounds.z_range(),
23            cube: if bounds.is_empty() {
24                // The next() algorithm assumes that if self.cube.x is in self.x_range then that
25                // cube should be produced, but this is true only in the nonempty case.
26                bounds.upper_bounds()
27            } else {
28                bounds.lower_bounds()
29            },
30        }
31    }
32
33    /// Returns the bounds which this iterator iterates over.
34    /// This may be larger than the union of produced cubes, but it will not be smaller.
35    #[inline]
36    pub fn bounds(&self) -> GridAab {
37        GridAab::from_ranges([
38            self.x_range.clone(),
39            self.y_range.clone(),
40            self.z_range.clone(),
41        ])
42    }
43
44    /// Returns whether the iterator will produce the given cube.
45    #[inline]
46    pub fn contains_cube(&self, cube: Cube) -> bool {
47        if !self.bounds().contains_cube(cube) {
48            return false;
49        }
50        match cube.x.cmp(&self.cube.x) {
51            Ordering::Greater => true, // in a plane not yet emitted
52            Ordering::Less => false,   // in a plane already emitted
53            Ordering::Equal => {
54                match cube.y.cmp(&self.cube.y) {
55                    Ordering::Greater => true, // in a row not yet emitted
56                    Ordering::Less => false,   // in a row already emitted
57                    Ordering::Equal => {
58                        // We have now reduced to the single-dimensional case.
59                        cube.z >= self.cube.z
60                    }
61                }
62            }
63        }
64    }
65}
66
67impl Iterator for GridIter {
68    type Item = Cube;
69
70    #[inline]
71    fn next(&mut self) -> Option<Self::Item> {
72        if self.cube.x >= self.x_range.end {
73            return None;
74        }
75        let result = self.cube;
76        self.cube.z += 1;
77        if self.cube.z >= self.z_range.end {
78            self.cube.z = self.z_range.start;
79            self.cube.y += 1;
80            if self.cube.y >= self.y_range.end {
81                self.cube.y = self.y_range.start;
82                self.cube.x += 1;
83                // When x becomes out of bounds, that signals the end.
84            }
85        }
86        Some(result.into())
87    }
88
89    #[allow(clippy::missing_inline_in_public_items, reason = "unclear benefit")]
90    fn size_hint(&self) -> (usize, Option<usize>) {
91        match usize::try_from((self.x_range.end - self.cube.x) - 1) {
92            Err(_) => {
93                // x has hit the end, no items left
94                (0, Some(0))
95            }
96            Ok(planes_remaining) => {
97                let rows_remaining = planes_remaining * self.y_range.len()
98                    + usize::try_from((self.y_range.end - self.cube.y) - 1).unwrap_or(0);
99                let cubes_remaining = rows_remaining * self.z_range.len()
100                    + usize::try_from(self.z_range.end - self.cube.z).unwrap();
101
102                (cubes_remaining, Some(cubes_remaining))
103            }
104        }
105    }
106
107    // Override fold() to achieve greater performance via simpler iteration.
108    #[allow(clippy::missing_inline_in_public_items, reason = "is generic already")]
109    fn fold<B, F>(mut self, init: B, mut f: F) -> B
110    where
111        F: FnMut(B, Self::Item) -> B,
112    {
113        let mut state = init;
114
115        // First, if the iterator has already been partly advanced (this is atypical),
116        // advance it until the remaining elements form an AAB.
117        #[cold]
118        #[inline(never)]
119        fn cold_next(i: &mut GridIter) -> Option<Cube> {
120            i.next()
121        }
122        while self.cube.y != self.y_range.start || self.cube.z != self.z_range.start {
123            let Some(cube) = cold_next(&mut self) else {
124                return state;
125            };
126            state = f(state, cube);
127        }
128
129        // Now, we can perform iteration over the numeric ranges independently,
130        // with no additional checks.
131        for x in self.cube.x..self.x_range.end {
132            for y in self.y_range.clone() {
133                for z in self.z_range.clone() {
134                    state = f(state, Cube::new(x, y, z));
135                }
136            }
137        }
138
139        state
140    }
141}
142
143impl ExactSizeIterator for GridIter {}
144impl FusedIterator for GridIter {}
145
146#[cfg(test)]
147mod tests {
148    use super::*;
149    use alloc::vec::Vec;
150
151    #[test]
152    fn zero_items() {
153        fn assert_no_items(b: GridAab) {
154            assert_eq!(b.interior_iter().collect::<Vec<_>>(), vec![], "{b:?}");
155        }
156
157        assert_no_items(GridAab::from_lower_size([0, 0, 0], [0, 0, 0]));
158        assert_no_items(GridAab::from_lower_size([0, 0, 0], [0, 0, 1]));
159        assert_no_items(GridAab::from_lower_size([0, 0, 0], [0, 1, 0]));
160        assert_no_items(GridAab::from_lower_size([0, 0, 0], [0, 1, 1]));
161        assert_no_items(GridAab::from_lower_size([0, 0, 0], [1, 0, 0]));
162        assert_no_items(GridAab::from_lower_size([0, 0, 0], [1, 0, 1]));
163        assert_no_items(GridAab::from_lower_size([0, 0, 0], [1, 1, 0]));
164    }
165
166    #[test]
167    fn grid_iter_size_hint() {
168        let b = GridAab::from_lower_size([0, 0, 0], [12, 34, 56]);
169        let expected_size = b.volume().unwrap();
170        let mut iter = b.interior_iter();
171
172        // Exact at start
173        assert_eq!(iter.size_hint(), (expected_size, Some(expected_size)));
174
175        for remaining in (1..=expected_size).rev() {
176            assert_eq!(iter.size_hint(), (remaining, Some(remaining)));
177            assert!(iter.next().is_some());
178        }
179
180        // Exact at end
181        assert_eq!(iter.size_hint(), (0, Some(0)));
182        assert!(iter.next().is_none());
183        assert_eq!(iter.size_hint(), (0, Some(0)));
184    }
185
186    #[test]
187    fn next_and_fold_are_equivalent() {
188        let b = GridAab::from_lower_size([0, -1, 7], [3, 3, 3]);
189        println!("Aab = {b:?}");
190
191        for start_point in 0..=b.volume().unwrap() {
192            println!("\nSkipping {start_point}:");
193            let mut iter_to_next = b.interior_iter().skip(start_point);
194            let iter_to_fold = b.interior_iter().skip(start_point);
195            iter_to_fold.fold((), |(), fold_cube| {
196                let next_cube = iter_to_next.next();
197                println!("fold={fold_cube:?} next={next_cube:?}");
198                assert_eq!(fold_cube, next_cube.unwrap());
199            });
200            assert_eq!(iter_to_next.next(), None, "finish");
201        }
202    }
203
204    #[test]
205    fn contains_cube() {
206        let b = GridAab::from_lower_size([0, 0, 0], [3, 3, 3]);
207        let expected_sequence: Vec<Cube> = b.interior_iter().collect();
208
209        let mut iter = b.interior_iter();
210        for current in 0..expected_sequence.len() {
211            for &cube in &expected_sequence[..current] {
212                assert!(
213                    !iter.contains_cube(cube),
214                    "{cube:?} should be absent at {current}"
215                );
216            }
217            for &cube in &expected_sequence[current..] {
218                assert!(
219                    iter.contains_cube(cube),
220                    "{cube:?} should be present at {current}"
221                );
222            }
223
224            let item = iter.next();
225
226            assert_eq!(item, Some(expected_sequence[current])); // sanity check, not what we're testing
227        }
228    }
229}