Skip to main content

int_interval/
u8.rs

1use crate::res::{OneTwo, ZeroOneTwo};
2
3#[cfg(test)]
4mod between_tests;
5#[cfg(test)]
6mod convex_hull_tests;
7#[cfg(test)]
8mod difference_tests;
9#[cfg(test)]
10mod intersection_tests;
11#[cfg(test)]
12mod symmetric_difference_tests;
13#[cfg(test)]
14mod transform_tests;
15#[cfg(test)]
16mod union_tests;
17
18#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
19pub struct U8CO {
20    start: u8,
21    end_excl: u8,
22}
23
24// ------------------------------------------------------------
25// low-level api: construction / accessors / predicates
26// ------------------------------------------------------------
27
28mod construction_accessors_predicates {
29
30    use super::*;
31
32    impl U8CO {
33        #[inline]
34        pub const fn try_new(start: u8, end_excl: u8) -> Option<Self> {
35            if start < end_excl {
36                Some(Self { start, end_excl })
37            } else {
38                None
39            }
40        }
41
42        #[inline]
43        pub(super) const fn new_unchecked(start: u8, end_excl: u8) -> Self {
44            debug_assert!(start < end_excl);
45            Self { start, end_excl }
46        }
47
48        #[inline]
49        pub const fn start(self) -> u8 {
50            self.start
51        }
52
53        #[inline]
54        pub const fn end_excl(self) -> u8 {
55            self.end_excl
56        }
57
58        #[inline]
59        pub const fn end_incl(self) -> u8 {
60            // u8_low_bound =< start < end_excl
61            self.end_excl - 1
62        }
63
64        #[inline]
65        pub const fn len(self) -> u8 {
66            self.end_excl - self.start
67        }
68
69        #[inline]
70        pub const fn contains(self, x: u8) -> bool {
71            self.start <= x && x < self.end_excl
72        }
73
74        #[inline]
75        pub const fn iter(self) -> core::ops::Range<u8> {
76            self.start..self.end_excl
77        }
78
79        #[inline]
80        pub const fn intersects(self, other: Self) -> bool {
81            !(self.end_excl <= other.start || other.end_excl <= self.start)
82        }
83
84        #[inline]
85        pub const fn is_adjacent(self, other: Self) -> bool {
86            self.end_excl == other.start || other.end_excl == self.start
87        }
88
89        #[inline]
90        pub const fn is_contiguous_with(self, other: Self) -> bool {
91            self.intersects(other) || self.is_adjacent(other)
92        }
93    }
94}
95
96// ------------------------------------------------------------
97// interval algebra api: intersection / convex_hull / between / union / difference / symmetric_difference
98// ------------------------------------------------------------
99
100mod interval_algebra {
101
102    use super::*;
103
104    impl U8CO {
105        /// Returns the intersection of two intervals.
106        ///
107        /// If the intervals do not overlap, returns `None`.
108        #[inline]
109        pub const fn intersection(self, other: Self) -> Option<Self> {
110            let start = if self.start >= other.start {
111                self.start
112            } else {
113                other.start
114            };
115
116            let end_excl = if self.end_excl <= other.end_excl {
117                self.end_excl
118            } else {
119                other.end_excl
120            };
121
122            Self::try_new(start, end_excl)
123        }
124
125        /// Returns the convex hull (smallest interval containing both) of two intervals.
126        ///
127        /// Always returns a valid `U8CO`.
128        #[inline]
129        pub const fn convex_hull(self, other: Self) -> Self {
130            let start = if self.start <= other.start {
131                self.start
132            } else {
133                other.start
134            };
135
136            let end_excl = if self.end_excl >= other.end_excl {
137                self.end_excl
138            } else {
139                other.end_excl
140            };
141
142            Self { start, end_excl }
143        }
144
145        /// Returns the interval strictly between two intervals.
146        ///
147        /// If the intervals are contiguous or overlap, returns `None`.
148        #[inline]
149        pub const fn between(self, other: Self) -> Option<Self> {
150            let (left, right) = if self.start <= other.start {
151                (self, other)
152            } else {
153                (other, self)
154            };
155
156            Self::try_new(left.end_excl, right.start)
157        }
158
159        /// Returns the union of two intervals.
160        ///
161        /// - If intervals are contiguous or overlapping, returns `One` containing the merged interval.
162        /// - Otherwise, returns `Two` containing both intervals in ascending order.
163        #[inline]
164        pub const fn union(self, other: Self) -> OneTwo<Self> {
165            if self.is_contiguous_with(other) {
166                OneTwo::One(self.convex_hull(other))
167            } else if self.start <= other.start {
168                OneTwo::Two(self, other)
169            } else {
170                OneTwo::Two(other, self)
171            }
172        }
173
174        /// Returns the difference of two intervals (self - other).
175        ///
176        /// - If no overlap, returns `One(self)`.
177        /// - If partial overlap, returns `One` or `Two` depending on remaining segments.
178        /// - If fully contained, returns `Zero`.
179        #[inline]
180        pub const fn difference(self, other: Self) -> ZeroOneTwo<Self> {
181            match self.intersection(other) {
182                None => ZeroOneTwo::One(self),
183                Some(inter) => {
184                    let left = Self::try_new(self.start, inter.start);
185                    let right = Self::try_new(inter.end_excl, self.end_excl);
186
187                    match (left, right) {
188                        (None, None) => ZeroOneTwo::Zero,
189                        (Some(x), None) | (None, Some(x)) => ZeroOneTwo::One(x),
190                        (Some(x), Some(y)) => ZeroOneTwo::Two(x, y),
191                    }
192                }
193            }
194        }
195
196        /// Returns the symmetric difference of two intervals.
197        ///
198        /// Equivalent to `(self - other) ∪ (other - self)`.
199        /// - If intervals do not overlap, returns `Two(self, other)` in order.
200        /// - If intervals partially overlap, returns remaining non-overlapping segments as `One` or `Two`.
201        #[inline]
202        pub const fn symmetric_difference(self, other: Self) -> ZeroOneTwo<Self> {
203            match self.intersection(other) {
204                None => {
205                    if self.start <= other.start {
206                        ZeroOneTwo::Two(self, other)
207                    } else {
208                        ZeroOneTwo::Two(other, self)
209                    }
210                }
211                Some(inter) => {
212                    let hull = self.convex_hull(other);
213                    let left = Self::try_new(hull.start, inter.start);
214                    let right = Self::try_new(inter.end_excl, hull.end_excl);
215
216                    match (left, right) {
217                        (None, None) => ZeroOneTwo::Zero,
218                        (Some(x), None) | (None, Some(x)) => ZeroOneTwo::One(x),
219                        (Some(x), Some(y)) => ZeroOneTwo::Two(x, y),
220                    }
221                }
222            }
223        }
224    }
225}
226
227// ------------------------------------------------------------
228// interval arithmetic / transform api: scale / shift / scale_then_shift / shift_then_scale
229// ------------------------------------------------------------
230
231mod interval_arithmetic {
232
233    use super::*;
234
235    impl U8CO {
236        #[inline]
237        pub const fn scale(self, factor: u8) -> Option<Self> {
238            if factor == 0 || self.end_excl > u8::MAX / factor {
239                return None;
240            }
241            Some(U8CO::new_unchecked(
242                self.start * factor,
243                self.end_excl * factor,
244            ))
245        }
246
247        #[inline]
248        pub const fn shift(self, offset: u8) -> Option<Self> {
249            if self.end_excl > u8::MAX - offset {
250                return None;
251            }
252            Some(U8CO::new_unchecked(
253                self.start + offset,
254                self.end_excl + offset,
255            ))
256        }
257    }
258}