Skip to main content

ps_range/
range.rs

1use crate::RangeStart;
2
3/// The upper bound of a [`Range`], either inclusive or exclusive.
4///
5/// Ordering is by boundary position: bounds compare by their index first, and
6/// on equal indices `Exclusive(n) < Inclusive(n)`, since an inclusive bound
7/// extends one element further. `Inclusive(n)` and `Exclusive(n + 1)` denote
8/// the same boundary but compare as unequal, with `Inclusive(n)` ordered
9/// first, consistent with [`Eq`].
10///
11/// # Examples
12///
13/// ```
14/// use ps_range::RangeEnd;
15///
16/// assert!(RangeEnd::Exclusive(5) < RangeEnd::Inclusive(5));
17/// assert!(RangeEnd::Inclusive(4) < RangeEnd::Exclusive(5));
18/// ```
19#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
20pub enum RangeEnd<Idx> {
21    /// The end index is part of the range.
22    Inclusive(Idx),
23    /// The end index is not part of the range.
24    Exclusive(Idx),
25}
26
27const fn index<Idx>(end: &RangeEnd<Idx>) -> &Idx {
28    match end {
29        RangeEnd::Inclusive(index) | RangeEnd::Exclusive(index) => index,
30    }
31}
32
33const fn rank<Idx>(end: &RangeEnd<Idx>) -> u8 {
34    match end {
35        RangeEnd::Exclusive(_) => 0,
36        RangeEnd::Inclusive(_) => 1,
37    }
38}
39
40impl<Idx: Ord> Ord for RangeEnd<Idx> {
41    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
42        index(self)
43            .cmp(index(other))
44            .then_with(|| rank(self).cmp(&rank(other)))
45    }
46}
47
48impl<Idx: PartialOrd> PartialOrd for RangeEnd<Idx> {
49    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
50        index(self)
51            .partial_cmp(index(other))
52            .map(|ordering| ordering.then_with(|| rank(self).cmp(&rank(other))))
53    }
54}
55
56/// A range bounded on both ends, with an inclusive start and an end whose
57/// inclusivity is recorded by [`RangeEnd`].
58///
59/// Ordering is lexicographic: by start first, then by end.
60///
61/// # Examples
62///
63/// ```
64/// use ps_range::Range;
65///
66/// assert_eq!(Range::from(2..7), Range::exclusive(2, 7));
67/// assert_ne!(Range::inclusive(2, 6), Range::exclusive(2, 7));
68/// ```
69#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
70pub struct Range<Idx> {
71    /// The inclusive lower bound.
72    pub start: Idx,
73    /// The upper bound, either inclusive or exclusive.
74    pub end: RangeEnd<Idx>,
75}
76
77impl<Idx> Range<Idx> {
78    /// Creates a range from a start index and an upper bound.
79    #[inline]
80    #[must_use]
81    pub const fn new(start: Idx, end: RangeEnd<Idx>) -> Self {
82        Self { start, end }
83    }
84
85    /// Creates the range `start..=end`.
86    #[inline]
87    #[must_use]
88    pub const fn inclusive(start: Idx, end: Idx) -> Self {
89        Self::new(start, RangeEnd::Inclusive(end))
90    }
91
92    /// Creates the range `start..end`.
93    #[inline]
94    #[must_use]
95    pub const fn exclusive(start: Idx, end: Idx) -> Self {
96        Self::new(start, RangeEnd::Exclusive(end))
97    }
98}
99
100impl<Idx: Clone> RangeStart<Idx> for Range<Idx> {
101    #[inline]
102    fn start(&self) -> Idx {
103        self.start.clone()
104    }
105}
106
107impl<Idx: Clone + Ord> crate::PartialRangeExt<Idx> for Range<Idx> {
108    #[inline]
109    fn end_bound(&self) -> Option<RangeEnd<Idx>> {
110        Some(self.end.clone())
111    }
112}
113
114impl<Idx> From<std::ops::Range<Idx>> for Range<Idx> {
115    #[inline]
116    fn from(value: std::ops::Range<Idx>) -> Self {
117        Self::exclusive(value.start, value.end)
118    }
119}
120
121/// Converts the range, canonicalizing an empty source to an empty exclusive
122/// range at its start. The endpoint values of a drained
123/// [`std::ops::RangeInclusive`] are unspecified, so the position of the
124/// resulting empty range is likewise unspecified; only emptiness is
125/// guaranteed.
126impl<Idx: Clone + PartialOrd> From<std::ops::RangeInclusive<Idx>> for Range<Idx> {
127    #[inline]
128    fn from(value: std::ops::RangeInclusive<Idx>) -> Self {
129        let empty = value.is_empty();
130        let (start, end) = value.into_inner();
131
132        if empty {
133            Self::exclusive(start.clone(), start)
134        } else {
135            Self::inclusive(start, end)
136        }
137    }
138}