gridly/
range.rs

1//! Range types, similar to [`core::ops::Range`], for easy iteration over
2//! [`Row`], [`Column`], and [`Location`] values.
3//!
4//! [`Row`]: crate::location::Row
5//! [`Column`]: crate::location::Column
6//! [`Location`]: crate::location::Location
7
8use core::cmp::Ordering;
9use core::fmt::{self, Display, Formatter};
10use core::iter::FusedIterator;
11use core::marker::PhantomData;
12use core::ops::Range;
13
14use crate::location::{Column, Component, Location, LocationLike, Row};
15use crate::vector::Component as VecComponent;
16
17// TODO: replace this with ops::Range<C> once Step is stabilized. Mostly
18// we want this so that we can take advantage of `Range`'s very optimized
19// iterators.
20/// A range over [`Row`] or [`Column`] values. Much like the standard rust
21/// [`Range`](::core::ops::Range), it is half open, bounded by `[start..end)`.
22/// It supports simple accessors and iteration. It also forms the basis
23/// for bounds checking, through the [`check`][ComponentRange::check] method.
24#[derive(Debug, Clone, Eq, PartialEq, Hash)]
25pub struct ComponentRange<C: Component> {
26    range: Range<isize>,
27    phanton: PhantomData<C>,
28}
29
30impl<C: Component> ComponentRange<C> {
31    /// Create a range bounded by `[start .. end)`.
32    ///
33    /// # Example:
34    ///
35    /// ```
36    /// use gridly::range::ComponentRange;
37    /// use gridly::location::Row;
38    /// use gridly::vector::Rows;
39    ///
40    /// let mut range = ComponentRange::bounded(Row(0), Row(3));
41    ///
42    /// assert_eq!(range.size(), Rows(3));
43    /// assert_eq!(range.start(), Row(0));
44    /// assert_eq!(range.end(), Row(3));
45    ///
46    /// assert_eq!(range.next(), Some(Row(0)));
47    /// assert_eq!(range.next(), Some(Row(1)));
48    /// assert_eq!(range.next(), Some(Row(2)));
49    /// assert_eq!(range.next(), None);
50    /// ```
51    #[must_use]
52    #[inline]
53    pub fn bounded(start: C, end: C) -> Self {
54        ComponentRange {
55            phanton: PhantomData,
56            range: start.value()..end.value(),
57        }
58    }
59
60    /// Create a range starting at `start` with length `size`
61    ///
62    /// # Example:
63    ///
64    /// ```
65    /// use gridly::range::ComponentRange;
66    /// use gridly::location::Column;
67    /// use gridly::vector::Columns;
68    ///
69    /// let mut range = ComponentRange::span(Column(1), Columns(2));
70    ///
71    /// assert_eq!(range.size(), Columns(2));
72    /// assert_eq!(range.start(), Column(1));
73    /// assert_eq!(range.end(), Column(3));
74    ///
75    /// assert_eq!(range.next(), Some(Column(1)));
76    /// assert_eq!(range.next(), Some(Column(2)));
77    /// assert_eq!(range.next(), None);
78    /// ```
79    #[must_use]
80    #[inline]
81    pub fn span(start: C, size: C::Distance) -> Self {
82        Self::bounded(start, start.add_distance(size))
83    }
84
85    /// Get the start index of the range
86    #[must_use]
87    #[inline]
88    pub fn start(&self) -> C {
89        self.range.start.into()
90    }
91
92    /// Get the end index of the range
93    #[must_use]
94    #[inline]
95    pub fn end(&self) -> C {
96        self.range.end.into()
97    }
98
99    /// Get the size of the range
100    ///
101    /// # Example:
102    ///
103    /// ```
104    /// use gridly::range::ComponentRange;
105    /// use gridly::location::Row;
106    /// use gridly::vector::Rows;
107    ///
108    /// let range = ComponentRange::bounded(Row(-1), Row(3));
109    /// assert_eq!(range.size(), Rows(4));
110    /// ```
111    #[must_use]
112    #[inline]
113    pub fn size(&self) -> C::Distance {
114        self.start().distance_to(self.end())
115    }
116
117    /// Check that a `Row` or `Column` is in bounds for this range. If it is,
118    /// return the index as a `Row` or `Column`; otherwise, return a `RangeError`
119    /// indivating if the index was too high or too low, and what the exceeded
120    /// upper or lower bound is.
121    ///
122    /// # Example:
123    ///
124    /// ```
125    /// use gridly::range::{ComponentRange, RangeError};
126    /// use gridly::location::Row;
127    /// use gridly::vector::Rows;
128    ///
129    /// let range = ComponentRange::span(Row(3), Rows(5));
130    /// assert_eq!(range.check(Row(4)), Ok(Row(4)));
131    /// assert_eq!(range.check(Row(0)), Err(RangeError::TooLow(Row(3))));
132    /// assert_eq!(range.check(Row(8)), Err(RangeError::TooHigh(Row(8))));
133    ///
134    /// // This can also be used to quickly map an `isize` to a `Row` or `Column`
135    /// assert_eq!(range.check(5), Ok(Row(5)));
136    /// ```
137    pub fn check(&self, idx: impl Into<C>) -> Result<C, RangeError<C>> {
138        let idx = idx.into();
139
140        let min = self.start();
141        let max = self.end();
142
143        if idx < min {
144            Err(RangeError::TooLow(min))
145        } else if idx >= max {
146            Err(RangeError::TooHigh(max))
147        } else {
148            Ok(idx)
149        }
150    }
151
152    /// Check that a `Row` or `Column` is in bounds for this range.
153    #[must_use]
154    #[inline]
155    pub fn in_bounds(&self, loc: impl Into<C>) -> bool {
156        self.check(loc).is_ok()
157    }
158
159    /// Combine an index range with a converse index to create a [`LocationRange`]
160    ///
161    /// # Example:
162    ///
163    /// ```
164    /// use gridly::range::RowRange;
165    /// use gridly::location::{Row, Column};
166    /// use gridly::shorthand::L;
167    ///
168    /// let row_range = RowRange::bounded(Row(3), Row(6));
169    /// let mut loc_range = row_range.cross(Column(4));
170    ///
171    /// assert_eq!(loc_range.next(), Some(L(3, 4)));
172    /// assert_eq!(loc_range.next(), Some(L(4, 4)));
173    /// assert_eq!(loc_range.next(), Some(L(5, 4)));
174    /// assert_eq!(loc_range.next(), None);
175    /// ```
176    #[must_use]
177    #[inline]
178    pub fn cross(self, index: C::Converse) -> LocationRange<C::Converse> {
179        LocationRange::new(index, self)
180    }
181}
182
183// TODO: impl RangeBounds for ComponentRange.
184
185// TODO: add a bunch more iterator methods that forward to self.range;
186impl<C: Component> Iterator for ComponentRange<C> {
187    type Item = C;
188
189    #[inline]
190    fn next(&mut self) -> Option<C> {
191        self.range.next().map(C::from)
192    }
193
194    #[must_use]
195    #[inline]
196    fn size_hint(&self) -> (usize, Option<usize>) {
197        self.range.size_hint()
198    }
199
200    #[inline]
201    fn nth(&mut self, n: usize) -> Option<C> {
202        self.range.nth(n).map(C::from)
203    }
204
205    #[inline]
206    fn last(mut self) -> Option<C> {
207        self.next_back()
208    }
209}
210
211impl<C: Component> DoubleEndedIterator for ComponentRange<C> {
212    #[inline]
213    fn next_back(&mut self) -> Option<C> {
214        self.range.next_back().map(C::from)
215    }
216}
217
218impl<C: Component> ExactSizeIterator for ComponentRange<C> {}
219impl<C: Component> FusedIterator for ComponentRange<C> {}
220// TODO: TrustedLen when stable
221
222pub type RowRange = ComponentRange<Row>;
223pub type ColumnRange = ComponentRange<Column>;
224
225/// Error indicating that a Row or Column was out of bounds.
226///
227/// Note that the bounds expressed in this error are half inclusive; that is,
228/// the lower bound in TooLow is an inclusive lower bound, but the upper bound
229/// in TooHigh is an exclusive upper bound. This is consistent with the
230/// conventional range representation of `low..high`.
231///
232/// # Example:
233///
234/// ```
235/// use gridly::range::{ComponentRange, RangeError};
236/// use gridly::location::Row;
237/// let range = ComponentRange::bounded(Row(0), Row(10));
238///
239///
240/// assert_eq!(range.check(-5), Err(RangeError::TooLow(Row(0))));
241/// assert_eq!(range.check(15), Err(RangeError::TooHigh(Row(10))));
242/// assert_eq!(range.check(10), Err(RangeError::TooHigh(Row(10))));
243/// ```
244#[derive(Debug, Copy, Clone, PartialEq, Eq)]
245pub enum RangeError<T: Component> {
246    /// The given row or column was too low. The value in the error is the
247    /// minimum row or column, inclusive.
248    TooLow(T),
249
250    /// The given row or column was too high. The given value in the error is
251    /// the maximum row or column, exclusive (that is, a value *equal* to the
252    /// error value is considered too high).
253    TooHigh(T),
254}
255
256impl<T: Component> Display for RangeError<T> {
257    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
258        match self {
259            RangeError::TooLow(min) => write!(f, "{} too low, must be >= {:?}", T::name(), min),
260            RangeError::TooHigh(max) => write!(f, "{} too high, must be < {:?}", T::name(), max),
261        }
262    }
263}
264
265// TODO: Add this when we figure out how to make it compatible with no_std
266/* impl<T: Component> Error for Component {} */
267
268pub type RowRangeError = RangeError<Row>;
269pub type ColumnRangeError = RangeError<Column>;
270
271/// A range over [`Location`]s in a given [`Row`] or [`Column`].
272///
273/// The generic parameter is the direction of the range; that is to say, a
274/// `LocationRange<Row>` is a range of locations in a given row. Each location
275/// in the range has the same `row` but a different `column`.
276#[derive(Debug, Clone, Eq, PartialEq, Hash)]
277pub struct LocationRange<C: Component> {
278    index: C,
279    range: ComponentRange<C::Converse>,
280}
281
282impl<C: Component> LocationRange<C> {
283    #[inline]
284    #[must_use]
285    pub fn new(index: C, range: ComponentRange<C::Converse>) -> Self {
286        LocationRange { index, range }
287    }
288
289    #[inline]
290    #[must_use]
291    pub fn bounded(index: C, start: C::Converse, end: C::Converse) -> Self {
292        Self::new(index, ComponentRange::bounded(start, end))
293    }
294
295    #[inline]
296    #[must_use]
297    pub fn span(index: C, start: C::Converse, size: <C::Converse as Component>::Distance) -> Self {
298        Self::new(index, ComponentRange::span(start, size))
299    }
300
301    #[inline]
302    #[must_use]
303    pub fn rooted(root: Location, size: <C::Converse as Component>::Distance) -> Self {
304        Self::span(root.get_component(), root.get_component(), size)
305    }
306
307    #[inline]
308    #[must_use]
309    pub fn component_range(&self) -> ComponentRange<C::Converse> {
310        self.range.clone()
311    }
312
313    #[inline]
314    #[must_use]
315    pub fn index(&self) -> C {
316        self.index
317    }
318
319    #[inline]
320    #[must_use]
321    pub fn start(&self) -> Location {
322        self.range.start().combine(self.index)
323    }
324
325    #[inline]
326    #[must_use]
327    pub fn end(&self) -> Location {
328        self.range.end().combine(self.index)
329    }
330
331    #[inline]
332    #[must_use]
333    pub fn size(&self) -> <C::Converse as Component>::Distance {
334        self.range.start().distance_to(self.range.end())
335    }
336}
337
338impl LocationRange<Row> {
339    #[inline]
340    #[must_use]
341    pub fn row(&self) -> Row {
342        self.index
343    }
344
345    #[inline]
346    #[must_use]
347    pub fn columns(&self) -> ColumnRange {
348        self.component_range()
349    }
350}
351
352impl LocationRange<Column> {
353    #[inline]
354    #[must_use]
355    pub fn column(&self) -> Column {
356        self.index
357    }
358
359    #[inline]
360    #[must_use]
361    pub fn rows(&self) -> RowRange {
362        self.component_range()
363    }
364}
365
366impl<C: Component> Iterator for LocationRange<C> {
367    type Item = Location;
368
369    #[inline]
370    fn next(&mut self) -> Option<Location> {
371        self.range
372            .next()
373            .map(move |cross| cross.combine(self.index))
374    }
375
376    #[must_use]
377    #[inline]
378    fn size_hint(&self) -> (usize, Option<usize>) {
379        self.range.size_hint()
380    }
381
382    #[inline]
383    fn nth(&mut self, n: usize) -> Option<Location> {
384        self.range
385            .nth(n)
386            .map(move |cross| cross.combine(self.index))
387    }
388
389    #[inline]
390    fn last(self) -> Option<Location> {
391        let index = self.index;
392        self.range.last().map(move |cross| cross.combine(index))
393    }
394}
395
396impl<C: Component> DoubleEndedIterator for LocationRange<C> {
397    fn next_back(&mut self) -> Option<Self::Item> {
398        self.range
399            .next_back()
400            .map(move |cross| cross.combine(self.index))
401    }
402}
403
404impl<C: Component> FusedIterator for LocationRange<C> {}
405impl<C: Component> ExactSizeIterator for LocationRange<C> {}
406// TODO: TrustedLen
407
408/// A range over `Locations`, in row or column-major order.
409#[derive(Debug, Clone, Eq, PartialEq, Hash)]
410pub struct CrossRange<C: Component> {
411    // The nomenclature here assume row-major, to aid in readability.
412
413    // Our current top component
414    top: C,
415
416    // Our current bottom component. Inclusive; may == top.
417    bottom: C,
418
419    // This is the span we reset to each time we step top or bottom
420    span: ComponentRange<C::Converse>,
421
422    next_front: C::Converse,
423
424    // Exclusive outer bound
425    next_back: C::Converse,
426}
427
428impl<C: Component> CrossRange<C> {
429    #[must_use]
430    #[inline]
431    pub fn new(major: ComponentRange<C>, cross: ComponentRange<C::Converse>) -> Self {
432        Self {
433            top: major.start(),
434            bottom: major.end().add_distance(-1),
435
436            next_front: cross.start(),
437            next_back: cross.end(),
438
439            span: cross,
440        }
441    }
442
443    /// Count the remianing elements in this iterator. Helper for size_hint.
444    #[must_use]
445    fn size(&self) -> Option<usize> {
446        match self.top.cmp(&self.bottom) {
447            Ordering::Greater => Some(0),
448            Ordering::Equal => Some(self.next_front.distance_to(self.next_back).value() as usize),
449            Ordering::Less => {
450                // Safety: all of the ranges here are guaranteed to be positive or zero
451                // by the contract of this struct. top < bottom, so the -1 is safe.
452                let hamburger_thickness = (self.top.distance_to(self.bottom).value() as usize) - 1;
453                let hamburger_size =
454                    hamburger_thickness.checked_mul(self.span.size().value() as usize)?;
455
456                let top_bun = self.next_front.distance_to(self.span.end()).value() as usize;
457                let bottom_bun = self.span.start().distance_to(self.next_back).value() as usize;
458
459                hamburger_size.checked_add(top_bun)?.checked_add(bottom_bun)
460            }
461        }
462    }
463}
464
465impl<C: Component> Iterator for CrossRange<C> {
466    type Item = Location;
467
468    fn next(&mut self) -> Option<Location> {
469        loop {
470            match self.top.cmp(&self.bottom) {
471                Ordering::Greater => break None,
472                Ordering::Equal if self.next_front >= self.next_back => break None,
473                Ordering::Less if self.next_front >= self.span.end() => {
474                    self.top = self.top.add_distance(1);
475                    self.next_front = self.span.start();
476                }
477                _ => {
478                    let index = self.next_front;
479                    self.next_front = self.next_front.add_distance(1);
480                    break Some(index.combine(self.top));
481                }
482            }
483        }
484    }
485
486    #[must_use]
487    #[inline]
488    fn size_hint(&self) -> (usize, Option<usize>) {
489        let size = self.size();
490
491        (size.unwrap_or(core::usize::MAX), size)
492    }
493}
494
495impl<C: Component> DoubleEndedIterator for CrossRange<C> {
496    fn next_back(&mut self) -> Option<Location> {
497        loop {
498            match self.top.cmp(&self.bottom) {
499                Ordering::Greater => break None,
500                Ordering::Equal if self.next_front >= self.next_back => break None,
501                Ordering::Less if self.next_back <= self.span.start() => {
502                    self.bottom = self.bottom.add_distance(-1);
503                    self.next_back = self.span.end();
504                }
505                _ => {
506                    self.next_back = self.next_back.add_distance(-1);
507                    break Some(self.next_back.combine(self.bottom));
508                }
509            }
510        }
511    }
512}
513
514impl<C: Component> FusedIterator for CrossRange<C> {}
515impl<C: Component> ExactSizeIterator for CrossRange<C> {}
516
517#[test]
518fn test_cross_range() {
519    use crate::vector::{Columns, Rows};
520
521    let row_range = RowRange::span(Row(-2), Rows(5));
522    let column_range = ColumnRange::span(Column(3), Columns(4));
523
524    let mut cross_range = CrossRange::new(row_range.clone(), column_range.clone());
525
526    let mut remaining = 20;
527
528    assert_eq!(cross_range.size_hint(), (20, Some(20)));
529
530    for row in row_range {
531        for column in column_range.clone() {
532            let actual = cross_range.next();
533            remaining -= 1;
534
535            assert_eq!(Some(row + column), actual);
536            assert_eq!(cross_range.size_hint(), (remaining, Some(remaining)));
537        }
538    }
539
540    assert_eq!(cross_range.next(), None);
541}
542
543#[test]
544fn test_cross_range_reverse() {
545    use crate::vector::{Columns, Rows};
546
547    let row_range = RowRange::span(Row(-2), Rows(5));
548    let column_range = ColumnRange::span(Column(3), Columns(4));
549
550    let mut cross_range = CrossRange::new(row_range.clone(), column_range.clone());
551
552    let mut remaining = 20;
553
554    assert_eq!(cross_range.size_hint(), (20, Some(20)));
555
556    for row in row_range.rev() {
557        for column in column_range.clone().rev() {
558            let actual = cross_range.next_back();
559            remaining -= 1;
560
561            assert_eq!(Some(row + column), actual);
562            assert_eq!(cross_range.size_hint(), (remaining, Some(remaining)));
563        }
564    }
565
566    assert_eq!(cross_range.next_back(), None);
567}
568
569/// Test that iteration and size hint are correct even when the iterator
570/// is being iterated from both ends.
571#[test]
572fn test_cross_range_converge() {
573    use crate::vector::{Columns, Rows};
574
575    let row_range = RowRange::span(Row(-2), Rows(5));
576    let column_range = ColumnRange::span(Column(3), Columns(4));
577
578    let mut converging_cross_range = CrossRange::new(row_range, column_range);
579
580    let mut front_cross_range = converging_cross_range.clone();
581    let mut back_cross_range = converging_cross_range.clone();
582
583    let mut remaining = 20;
584
585    while remaining > 0 {
586        let next_front = front_cross_range.next();
587        let converge_front = converging_cross_range.next();
588        remaining -= 1;
589
590        assert!(converge_front.is_some());
591        assert_eq!(next_front, converge_front);
592        assert_eq!(
593            converging_cross_range.size_hint(),
594            (remaining, Some(remaining))
595        );
596
597        let next_back = back_cross_range.next_back();
598        let converge_back = converging_cross_range.next_back();
599        remaining -= 1;
600
601        assert!(converge_back.is_some());
602        assert_eq!(next_back, converge_back);
603        assert_eq!(
604            converging_cross_range.size_hint(),
605            (remaining, Some(remaining))
606        );
607    }
608
609    assert_eq!(converging_cross_range.size_hint(), (0, Some(0)));
610    assert_eq!(converging_cross_range.next(), None);
611    assert_eq!(converging_cross_range.next_back(), None);
612    assert_eq!(converging_cross_range.size_hint(), (0, Some(0)));
613}