Skip to main content

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