Skip to main content

ps_range/
partial_range.rs

1use std::mem;
2
3use num_traits::{CheckedAdd, One, Zero};
4
5use crate::{Range, RangeEnd, RangeStart};
6
7/// A range that may be unbounded above, empty, or exhausted.
8///
9/// [`Empty`](PartialRange::Empty) records a range that never contained any
10/// indices, anchored at a position; [`Exhausted`](PartialRange::Exhausted)
11/// records a range fully consumed by iteration, anchored at no position. The
12/// two are empty in the same way but compare as unequal.
13///
14/// # Examples
15///
16/// ```
17/// use ps_range::PartialRange;
18///
19/// let mut range = PartialRange::from(5usize..8);
20///
21/// assert_eq!(range.by_ref().collect::<Vec<_>>(), vec![5, 6, 7]);
22/// assert_eq!(range, PartialRange::Exhausted);
23/// ```
24#[derive(Clone, Debug, Hash, PartialEq, Eq)]
25pub enum PartialRange<Idx> {
26    /// An empty range that never contained any indices.
27    Empty {
28        /// The position at which the empty range is anchored.
29        idx: Idx,
30    },
31    /// A range fully consumed by iteration, anchored at no position.
32    Exhausted,
33    /// A range unbounded above.
34    From {
35        /// The `start..` range.
36        inner: std::ops::RangeFrom<Idx>,
37    },
38    /// A bounded range that includes its end.
39    Inclusive {
40        /// The inclusive lower bound.
41        start: Idx,
42        /// The inclusive upper bound.
43        end: Idx,
44    },
45    /// A bounded range that excludes its end.
46    Exclusive {
47        /// The `start..end` range.
48        inner: std::ops::Range<Idx>,
49    },
50}
51
52impl<Idx: Clone + Zero> RangeStart<Idx> for PartialRange<Idx> {
53    #[inline]
54    fn start(&self) -> Idx {
55        match self {
56            Self::Empty { idx } => idx.clone(),
57            Self::Exhausted => Idx::zero(),
58            Self::From { inner } => inner.start.clone(),
59            Self::Inclusive { start, .. } => start.clone(),
60            Self::Exclusive { inner } => inner.start.clone(),
61        }
62    }
63}
64
65impl<Idx: Clone + Ord + Zero> crate::PartialRangeExt<Idx> for PartialRange<Idx> {
66    #[inline]
67    fn end_bound(&self) -> Option<RangeEnd<Idx>> {
68        match self {
69            Self::Empty { idx } => Some(RangeEnd::Exclusive(idx.clone())),
70            Self::Exhausted => Some(RangeEnd::Exclusive(Idx::zero())),
71            Self::From { .. } => None,
72            Self::Inclusive { end, .. } => Some(RangeEnd::Inclusive(end.clone())),
73            Self::Exclusive { inner } => Some(RangeEnd::Exclusive(inner.end.clone())),
74        }
75    }
76}
77
78/// The size hint is conservative: an empty range reports an exact zero, and
79/// a non-empty range reports a lower bound of one with an unknown upper
80/// bound, since computing the exact length would require subtraction and a
81/// conversion to `usize` beyond the deliberately minimal `Idx` bounds.
82///
83/// Iteration eventually leaves every range [`Exhausted`](PartialRange::Exhausted):
84/// draining the final element transitions there, and so does the first call
85/// on a range that has nothing to yield, discarding an
86/// [`Empty`](PartialRange::Empty) anchor. Iteration also makes strict
87/// progress: when `checked_add` fails, or yields a successor that is not
88/// strictly greater than the current index, as with a saturating addition at
89/// its cap, the range yields the current index and becomes exhausted instead
90/// of overflowing or repeating forever.
91impl<Idx> Iterator for PartialRange<Idx>
92where
93    Idx: CheckedAdd + Clone + One + PartialOrd,
94{
95    type Item = Idx;
96
97    fn next(&mut self) -> Option<Self::Item> {
98        match self {
99            Self::Exhausted => None,
100
101            Self::Empty { .. } => {
102                *self = Self::Exhausted;
103
104                None
105            }
106
107            Self::From { inner } => match inner.start.checked_add(&Idx::one()) {
108                Some(next) if next > inner.start => Some(mem::replace(&mut inner.start, next)),
109                _ => {
110                    let last = inner.start.clone();
111
112                    *self = Self::Exhausted;
113
114                    Some(last)
115                }
116            },
117
118            Self::Inclusive { start, end } => {
119                if *start <= *end {
120                    match start.checked_add(&Idx::one()) {
121                        Some(next) if next > *start && next <= *end => {
122                            Some(mem::replace(start, next))
123                        }
124                        _ => {
125                            let last = start.clone();
126
127                            *self = Self::Exhausted;
128
129                            Some(last)
130                        }
131                    }
132                } else {
133                    *self = Self::Exhausted;
134
135                    None
136                }
137            }
138
139            Self::Exclusive { inner } => {
140                if inner.start < inner.end {
141                    match inner.start.checked_add(&Idx::one()) {
142                        Some(next) if next > inner.start && next < inner.end => {
143                            Some(mem::replace(&mut inner.start, next))
144                        }
145                        _ => {
146                            let last = inner.start.clone();
147
148                            *self = Self::Exhausted;
149
150                            Some(last)
151                        }
152                    }
153                } else {
154                    *self = Self::Exhausted;
155
156                    None
157                }
158            }
159        }
160    }
161
162    fn size_hint(&self) -> (usize, Option<usize>) {
163        match self {
164            Self::Empty { .. } | Self::Exhausted => (0, Some(0)),
165
166            Self::From { .. } => (1, None),
167
168            Self::Inclusive { start, end } => {
169                if *start <= *end {
170                    (1, None)
171                } else {
172                    (0, Some(0))
173                }
174            }
175
176            Self::Exclusive { inner } => {
177                if inner.start < inner.end {
178                    (1, None)
179                } else {
180                    (0, Some(0))
181                }
182            }
183        }
184    }
185}
186
187impl<Idx> std::iter::FusedIterator for PartialRange<Idx> where
188    Idx: CheckedAdd + Clone + One + PartialOrd
189{
190}
191
192impl<Idx> From<std::ops::Range<Idx>> for PartialRange<Idx> {
193    #[inline]
194    fn from(value: std::ops::Range<Idx>) -> Self {
195        Self::Exclusive { inner: value }
196    }
197}
198
199impl<Idx> From<std::ops::RangeFrom<Idx>> for PartialRange<Idx> {
200    #[inline]
201    fn from(value: std::ops::RangeFrom<Idx>) -> Self {
202        Self::From { inner: value }
203    }
204}
205
206/// Converts the range, canonicalizing an empty source to the
207/// [`Empty`](PartialRange::Empty) variant anchored at its start. The
208/// endpoint values of a drained [`std::ops::RangeInclusive`] are
209/// unspecified, so the resulting anchor is likewise unspecified; only
210/// emptiness is guaranteed.
211impl<Idx: PartialOrd> From<std::ops::RangeInclusive<Idx>> for PartialRange<Idx> {
212    #[inline]
213    fn from(value: std::ops::RangeInclusive<Idx>) -> Self {
214        let empty = value.is_empty();
215        let (start, end) = value.into_inner();
216
217        if empty {
218            Self::Empty { idx: start }
219        } else {
220            Self::Inclusive { start, end }
221        }
222    }
223}
224
225impl<Idx> From<Range<Idx>> for PartialRange<Idx> {
226    #[inline]
227    fn from(value: Range<Idx>) -> Self {
228        match value.end {
229            RangeEnd::Inclusive(end) => Self::Inclusive {
230                start: value.start,
231                end,
232            },
233            RangeEnd::Exclusive(end) => Self::Exclusive {
234                inner: value.start..end,
235            },
236        }
237    }
238}
239
240impl<Idx> IntoIterator for Range<Idx>
241where
242    Idx: CheckedAdd + Clone + One + PartialOrd,
243{
244    type Item = Idx;
245    type IntoIter = PartialRange<Idx>;
246
247    #[inline]
248    fn into_iter(self) -> Self::IntoIter {
249        self.into()
250    }
251}