skeid/matrix/
iterator.rs

1//! iterating over matrices
2
3use crate::matrix::{Matrix, MatrixCoordinate};
4use std::iter::FusedIterator;
5
6/// An iterator that iterates coordinates of a matrix given start and end points,
7/// iterating each element in a column, then moving to the next column.
8#[derive(Debug)]
9pub struct MatrixAreaIterator {
10    start: MatrixCoordinate,
11    row_size: usize,
12    size: usize,
13    state: Option<(usize, usize)>,
14}
15
16impl MatrixAreaIterator {
17    /// construct a new MatrixAreaIterator given start and end coordinates
18    #[must_use]
19    pub fn new(start: MatrixCoordinate, end: MatrixCoordinate) -> Self {
20        Self {
21            start,
22            row_size: 1 + (end.row - start.row),
23            size: (1 + (end.column - start.column)) * (1 + (end.row - start.row)),
24            state: Some((0, 0)),
25        }
26    }
27}
28
29impl Iterator for MatrixAreaIterator {
30    type Item = MatrixCoordinate;
31
32    fn next(&mut self) -> Option<Self::Item> {
33        let next = match self.state {
34            Some((front, _)) => MatrixCoordinate::new(
35                front / self.row_size + self.start.column,
36                front % self.row_size + self.start.row,
37            ),
38            None => return None, // fused
39        };
40        self.state = match self.state {
41            Some((front, back)) if self.size - back > front + 1 => Some((front + 1, back)),
42            Some(_) => None, // trip fuse, the whole matrix has been iterated
43            None => None,    // fused
44        };
45        Some(next)
46    }
47
48    fn size_hint(&self) -> (usize, Option<usize>) {
49        (self.size, Some(self.size))
50    }
51}
52
53impl DoubleEndedIterator for MatrixAreaIterator {
54    fn next_back(&mut self) -> Option<Self::Item> {
55        let next = match self.state {
56            Some((_, back)) => {
57                let back_index = self.size - 1 - back;
58                MatrixCoordinate::new(
59                    back_index / self.row_size + self.start.column,
60                    back_index % self.row_size + self.start.row,
61                )
62            }
63            None => return None, // fused
64        };
65        self.state = match self.state {
66            Some((front, back)) if self.size - (back + 1) > front => Some((front, back + 1)),
67            Some(_) => None, // trip fuse, the whole matrix has been iterated
68            None => None,    // fused
69        };
70        Some(next)
71    }
72}
73
74impl FusedIterator for MatrixAreaIterator {}
75
76impl ExactSizeIterator for MatrixAreaIterator {}
77
78#[test]
79fn matrix_area_iterator_order() {
80    // 3x3 matrix
81    assert_eq!(
82        9,
83        MatrixAreaIterator::new(MatrixCoordinate::new(0, 0), MatrixCoordinate::new(2, 2))
84            .enumerate()
85            .map(|(i, coord)| assert_eq!(
86                coord,
87                MatrixCoordinate::new(i / 3, i % 3),
88                "Incorrect coordinate for this iteration."
89            ))
90            .count(),
91        "Expected iterator for 3x3 matrix area to iterate 9 times."
92    );
93    // 3x4 matrix
94    assert_eq!(
95        12,
96        MatrixAreaIterator::new(MatrixCoordinate::new(0, 0), MatrixCoordinate::new(3, 2))
97            .enumerate()
98            .map(|(i, coord)| assert_eq!(
99                coord,
100                MatrixCoordinate::new(i / 3, i % 3),
101                "Incorrect coordinate for this iteration."
102            ))
103            .count(),
104        "Expected iterator for 3x4 matrix area to iterate 12 times."
105    );
106    // 4x3 matrix
107    assert_eq!(
108        12,
109        MatrixAreaIterator::new(MatrixCoordinate::new(0, 0), MatrixCoordinate::new(2, 3))
110            .enumerate()
111            .map(|(i, coord)| assert_eq!(
112                coord,
113                MatrixCoordinate::new(i / 4, i % 4),
114                "Incorrect coordinate for this iteration."
115            ))
116            .count(),
117        "Expected iterator for 4x3 matrix area to iterate 12 times."
118    );
119}
120
121#[test]
122fn matrix_area_iterator_with_non_zero_start() {
123    // 3x3 matrix
124    assert_eq!(
125        9,
126        MatrixAreaIterator::new(MatrixCoordinate::new(3, 5), MatrixCoordinate::new(5, 7))
127            .enumerate()
128            .map(|(i, coord)| assert_eq!(
129                coord,
130                MatrixCoordinate::new(i / 3 + 3, i % 3 + 5),
131                "Incorrect coordinate for this iteration."
132            ))
133            .count(),
134        "Expected iterator for 3x3 matrix area to iterate 9 times."
135    );
136    // 3x4 matrix
137    assert_eq!(
138        12,
139        MatrixAreaIterator::new(MatrixCoordinate::new(3, 5), MatrixCoordinate::new(6, 7))
140            .enumerate()
141            .map(|(i, coord)| assert_eq!(
142                coord,
143                MatrixCoordinate::new(i / 3 + 3, i % 3 + 5),
144                "Incorrect coordinate for this iteration."
145            ))
146            .count(),
147        "Expected iterator for 3x4 matrix area to iterate 12 times."
148    );
149    // 4x3 matrix
150    assert_eq!(
151        12,
152        MatrixAreaIterator::new(MatrixCoordinate::new(3, 5), MatrixCoordinate::new(5, 8))
153            .enumerate()
154            .map(|(i, coord)| assert_eq!(
155                coord,
156                MatrixCoordinate::new(i / 4 + 3, i % 4 + 5),
157                "Incorrect coordinate for this iteration."
158            ))
159            .count(),
160        "Expected iterator for 4x3 matrix area to iterate 12 times."
161    );
162}
163
164#[test]
165fn matrix_area_iterator_reversed_order() {
166    // 3x3 matrix
167    assert_eq!(
168        9,
169        MatrixAreaIterator::new(MatrixCoordinate::new(0, 0), MatrixCoordinate::new(2, 2))
170            .enumerate()
171            .rev()
172            .map(|(i, coord)| assert_eq!(
173                coord,
174                MatrixCoordinate::new(i / 3, i % 3),
175                "Incorrect coordinate for this iteration."
176            ))
177            .count(),
178        "Expected iterator for 3x3 matrix area to iterate 9 times."
179    );
180    // 3x4 matrix
181    assert_eq!(
182        12,
183        MatrixAreaIterator::new(MatrixCoordinate::new(0, 0), MatrixCoordinate::new(3, 2))
184            .enumerate()
185            .rev()
186            .map(|(i, coord)| assert_eq!(
187                coord,
188                MatrixCoordinate::new(i / 3, i % 3),
189                "Incorrect coordinate for this iteration."
190            ))
191            .count(),
192        "Expected iterator for 3x4 matrix area to iterate 12 times."
193    );
194    // 4x3 matrix
195    assert_eq!(
196        12,
197        MatrixAreaIterator::new(MatrixCoordinate::new(0, 0), MatrixCoordinate::new(2, 3))
198            .enumerate()
199            .rev()
200            .map(|(i, coord)| assert_eq!(
201                coord,
202                MatrixCoordinate::new(i / 4, i % 4),
203                "Incorrect coordinate for this iteration."
204            ))
205            .count(),
206        "Expected iterator for 4x3 matrix area to iterate 12 times."
207    );
208}
209
210#[test]
211fn matrix_area_iterator_reversed_with_non_zero_start() {
212    // 3x3 matrix
213    assert_eq!(
214        9,
215        MatrixAreaIterator::new(MatrixCoordinate::new(3, 5), MatrixCoordinate::new(5, 7))
216            .enumerate()
217            .rev()
218            .map(|(i, coord)| assert_eq!(
219                coord,
220                MatrixCoordinate::new(i / 3 + 3, i % 3 + 5),
221                "Incorrect coordinate for this iteration."
222            ))
223            .count(),
224        "Expected iterator for 3x3 matrix area to iterate 9 times."
225    );
226    // 3x4 matrix
227    assert_eq!(
228        12,
229        MatrixAreaIterator::new(MatrixCoordinate::new(3, 5), MatrixCoordinate::new(6, 7))
230            .enumerate()
231            .rev()
232            .map(|(i, coord)| assert_eq!(
233                coord,
234                MatrixCoordinate::new(i / 3 + 3, i % 3 + 5),
235                "Incorrect coordinate for this iteration."
236            ))
237            .count(),
238        "Expected iterator for 3x4 matrix area to iterate 12 times."
239    );
240    // 4x3 matrix
241    assert_eq!(
242        12,
243        MatrixAreaIterator::new(MatrixCoordinate::new(3, 5), MatrixCoordinate::new(5, 8))
244            .enumerate()
245            .rev()
246            .map(|(i, coord)| assert_eq!(
247                coord,
248                MatrixCoordinate::new(i / 4 + 3, i % 4 + 5),
249                "Incorrect coordinate for this iteration."
250            ))
251            .count(),
252        "Expected iterator for 4x3 matrix area to iterate 12 times."
253    );
254}
255
256/// an iterator for consuming the elements of a `Matrix`
257#[derive(Debug)]
258pub struct MatrixIterator<T, const ROWS: usize, const COLUMNS: usize, I>
259where
260    T: Copy,
261    I: Iterator<Item = MatrixCoordinate>,
262{
263    pub(crate) matrix: Matrix<T, ROWS, COLUMNS>,
264    pub(crate) coordinate_iterator: I,
265}
266
267impl<T, const ROWS: usize, const COLUMNS: usize, I> Iterator for MatrixIterator<T, ROWS, COLUMNS, I>
268where
269    T: Copy,
270    I: Iterator<Item = MatrixCoordinate>,
271{
272    type Item = T;
273
274    fn next(&mut self) -> Option<Self::Item> {
275        let coordinate = match self.coordinate_iterator.next() {
276            Some(coord) => coord,
277            None => return None,
278        };
279        // we assume the index given is in bounds (if not, this panics)
280        Some(self.matrix[coordinate])
281    }
282
283    fn size_hint(&self) -> (usize, Option<usize>) {
284        self.coordinate_iterator.size_hint()
285    }
286}
287
288impl<T, const ROWS: usize, const COLUMNS: usize, I> DoubleEndedIterator
289    for MatrixIterator<T, ROWS, COLUMNS, I>
290where
291    T: Copy,
292    I: Iterator<Item = MatrixCoordinate> + DoubleEndedIterator,
293{
294    fn next_back(&mut self) -> Option<Self::Item> {
295        let coordinate = match self.coordinate_iterator.next_back() {
296            Some(coord) => coord,
297            None => return None,
298        };
299        // we assume the index given is in bounds (if not, this panics)
300        Some(self.matrix[coordinate])
301    }
302}
303
304impl<T, const ROWS: usize, const COLUMNS: usize, I> FusedIterator
305    for MatrixIterator<T, ROWS, COLUMNS, I>
306where
307    T: Copy,
308    I: Iterator<Item = MatrixCoordinate> + FusedIterator,
309{
310}
311
312impl<T, const ROWS: usize, const COLUMNS: usize, I> ExactSizeIterator
313    for MatrixIterator<T, ROWS, COLUMNS, I>
314where
315    T: Copy,
316    I: Iterator<Item = MatrixCoordinate> + ExactSizeIterator,
317{
318}
319
320impl<T, const ROWS: usize, const COLUMNS: usize> IntoIterator for Matrix<T, ROWS, COLUMNS>
321where
322    T: Copy,
323{
324    type Item = T;
325    type IntoIter = MatrixIterator<T, ROWS, COLUMNS, MatrixAreaIterator>;
326
327    fn into_iter(self) -> Self::IntoIter {
328        Self::IntoIter {
329            matrix: self,
330            coordinate_iterator: MatrixAreaIterator::new(
331                MatrixCoordinate::new(0, 0),
332                MatrixCoordinate::new(COLUMNS - 1, ROWS - 1),
333            ),
334        }
335    }
336}
337
338/// an iterator for reading (by reference) the elements of a `Matrix`
339#[derive(Debug)]
340pub struct MatrixReferenceIterator<'a, T, const ROWS: usize, const COLUMNS: usize, I>
341where
342    T: Copy,
343    I: Iterator<Item = MatrixCoordinate>,
344{
345    pub(crate) matrix: &'a Matrix<T, ROWS, COLUMNS>,
346    pub(crate) coordinate_iterator: I,
347}
348
349impl<'a, T, const ROWS: usize, const COLUMNS: usize, I> Iterator
350    for MatrixReferenceIterator<'a, T, ROWS, COLUMNS, I>
351where
352    T: Copy,
353    I: Iterator<Item = MatrixCoordinate>,
354{
355    type Item = &'a T;
356
357    fn next(&mut self) -> Option<Self::Item> {
358        let coordinate = match self.coordinate_iterator.next() {
359            Some(coord) => coord,
360            None => return None,
361        };
362        // we assume the index given is in bounds (if not, this panics)
363        Some(&self.matrix[coordinate])
364    }
365
366    fn size_hint(&self) -> (usize, Option<usize>) {
367        self.coordinate_iterator.size_hint()
368    }
369}
370
371impl<T, const ROWS: usize, const COLUMNS: usize, I> FusedIterator
372    for MatrixReferenceIterator<'_, T, ROWS, COLUMNS, I>
373where
374    T: Copy,
375    I: Iterator<Item = MatrixCoordinate> + FusedIterator,
376{
377}
378
379impl<T, const ROWS: usize, const COLUMNS: usize, I> ExactSizeIterator
380    for MatrixReferenceIterator<'_, T, ROWS, COLUMNS, I>
381where
382    T: Copy,
383    I: Iterator<Item = MatrixCoordinate> + ExactSizeIterator,
384{
385}
386
387impl<'a, T, const ROWS: usize, const COLUMNS: usize> IntoIterator for &'a Matrix<T, ROWS, COLUMNS>
388where
389    T: Copy,
390{
391    type Item = &'a T;
392    type IntoIter = MatrixReferenceIterator<'a, T, ROWS, COLUMNS, MatrixAreaIterator>;
393
394    fn into_iter(self) -> Self::IntoIter {
395        Self::IntoIter {
396            matrix: self,
397            coordinate_iterator: MatrixAreaIterator::new(
398                MatrixCoordinate::new(0, 0),
399                MatrixCoordinate::new(COLUMNS - 1, ROWS - 1),
400            ),
401        }
402    }
403}