numerated/
interval.rs

1// This file is part of Gear.
2
3// Copyright (C) 2023-2025 Gear Technologies Inc.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19//! [`Interval`] implementations.
20
21use crate::{
22    iterators::IntervalIterator,
23    numerated::{Bound, Numerated},
24};
25use core::{
26    fmt::{self, Debug, Formatter},
27    ops::{Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive},
28};
29use num_traits::{
30    CheckedAdd, One, Zero,
31    bounds::{LowerBounded, UpperBounded},
32};
33
34/// Describes not empty interval start..=end.
35#[derive(Clone, Copy, PartialEq, Eq, derive_more::Display)]
36#[display("{start}..={end}")]
37pub struct Interval<T> {
38    start: T,
39    end: T,
40}
41
42impl<T: Numerated> Interval<T> {
43    /// Creates new interval `start..=end` with checks only in debug mode.
44    ///
45    /// # Safety
46    ///
47    /// Unsafe, because allows to create invalid interval.
48    /// Safe, if `start ≤ end`.
49    #[track_caller]
50    pub unsafe fn new_unchecked(start: T, end: T) -> Self {
51        debug_assert!(
52            start <= end,
53            "Calling this method you must guarantee that `start ≤ end`"
54        );
55        Self { start, end }
56    }
57
58    /// Creates new interval start..=end if start ≤ end, else returns None.
59    pub fn new(start: T, end: T) -> Option<Self> {
60        (start <= end).then_some(Self { start, end })
61    }
62
63    /// Interval start (the smallest value inside interval)
64    pub fn start(&self) -> T {
65        self.start
66    }
67
68    /// Interval end (the biggest value inside interval)
69    pub fn end(&self) -> T {
70        self.end
71    }
72
73    /// Converts to [`IntervalIterator`].
74    pub fn iter(&self) -> IntervalIterator<T> {
75        (*self).into()
76    }
77
78    /// Into (start, end)
79    pub fn into_parts(self) -> (T, T) {
80        self.into()
81    }
82
83    /// Returns new [`Interval`] with `start` = `start` + 1, if it's possible.
84    pub fn inc_start(&self) -> Option<Self> {
85        let (start, end) = (self.start, self.end);
86        debug_assert!(start <= end, "It's guaranteed by `Interval`");
87        start.inc_if_lt(end).map(|start| {
88            debug_assert!(start <= end, "`T: Numerated` impl error");
89            Interval { start, end }
90        })
91    }
92
93    /// Trying to make [`Interval`] from `range`.
94    /// - If `range.start > range.end`, then returns [`IncorrectRangeError`].
95    /// - If `range.start == range.end`, then returns [`EmptyRangeError`].
96    pub fn try_from_range(range: Range<T>) -> Result<Self, TryFromRangeError> {
97        let (start, end) = (range.start, range.end);
98        end.dec_if_gt(start)
99            .map(|end| {
100                debug_assert!(start <= end, "`T: Numerated` impl error");
101                Self { start, end }
102            })
103            .ok_or(if start == end {
104                TryFromRangeError::EmptyRange
105            } else {
106                TryFromRangeError::IncorrectRange
107            })
108    }
109}
110
111impl<T: Numerated> PartialEq<RangeInclusive<T>> for Interval<T> {
112    fn eq(&self, other: &RangeInclusive<T>) -> bool {
113        let (start, end) = self.into_parts();
114        (start, end) == (*other.start(), *other.end())
115    }
116}
117
118impl<T: Numerated> From<Interval<T>> for (T, T) {
119    fn from(interval: Interval<T>) -> (T, T) {
120        (interval.start, interval.end)
121    }
122}
123
124impl<T: Numerated> From<Interval<T>> for RangeInclusive<T> {
125    fn from(interval: Interval<T>) -> Self {
126        interval.start..=interval.end
127    }
128}
129
130impl<T: Numerated> From<T> for Interval<T> {
131    fn from(point: T) -> Self {
132        let (start, end) = (point, point);
133        debug_assert!(start <= end, "`T: Ord` impl error");
134        Self { start, end }
135    }
136}
137
138impl<T: Numerated + LowerBounded> From<RangeToInclusive<T>> for Interval<T> {
139    fn from(range: RangeToInclusive<T>) -> Self {
140        let (start, end) = (T::min_value(), range.end);
141        debug_assert!(start <= end, "`T: LowerBounded` impl error");
142        Self { start, end }
143    }
144}
145
146impl<T: Numerated + UpperBounded + LowerBounded> From<RangeFull> for Interval<T> {
147    fn from(_: RangeFull) -> Self {
148        let (start, end) = (T::min_value(), T::max_value());
149        debug_assert!(start <= end, "`T: UpperBounded + LowerBounded` impl error");
150        Self { start, end }
151    }
152}
153
154/// Trying to make empty interval.
155#[derive(Debug, Clone, Copy, PartialEq, Eq)]
156pub struct EmptyRangeError;
157
158/// Trying to make interval from range where start > end.
159#[derive(Debug, Clone, Copy, PartialEq, Eq)]
160pub struct IncorrectRangeError;
161
162/// Trying to make interval from range where start > end or empty range.
163#[derive(Debug, Clone, Copy, PartialEq, Eq)]
164pub enum TryFromRangeError {
165    /// Trying to make empty interval.
166    EmptyRange,
167    /// Trying to make interval start > end.
168    IncorrectRange,
169}
170
171impl<T: Numerated + UpperBounded, I: Into<T::Bound>> TryFrom<RangeFrom<I>> for Interval<T> {
172    type Error = EmptyRangeError;
173
174    fn try_from(range: RangeFrom<I>) -> Result<Self, Self::Error> {
175        match Into::<T::Bound>::into(range.start).unbound() {
176            Some(start) => {
177                let end = T::max_value();
178                debug_assert!(start <= end, "`T: UpperBounded` impl error");
179                Ok(Self { start, end })
180            }
181            None => Err(EmptyRangeError),
182        }
183    }
184}
185
186impl<T: Numerated + LowerBounded + UpperBounded, I: Into<T::Bound>> TryFrom<RangeTo<I>>
187    for Interval<T>
188{
189    type Error = EmptyRangeError;
190
191    fn try_from(range: RangeTo<I>) -> Result<Self, Self::Error> {
192        let end: T::Bound = range.end.into();
193
194        let Some(end) = end.unbound() else {
195            return Ok(Self::from(..));
196        };
197
198        let start = T::min_value();
199        end.dec_if_gt(start)
200            .map(|end| {
201                debug_assert!(start <= end, "`T: LowerBounded` impl error");
202                Self { start, end }
203            })
204            .ok_or(EmptyRangeError)
205    }
206}
207
208impl<T: Numerated> TryFrom<RangeInclusive<T>> for Interval<T> {
209    type Error = IncorrectRangeError;
210
211    fn try_from(range: RangeInclusive<T>) -> Result<Self, Self::Error> {
212        let (start, end) = range.into_inner();
213        (start <= end)
214            .then_some(Self { start, end })
215            .ok_or(IncorrectRangeError)
216    }
217}
218
219impl<T, S, E> TryFrom<(S, E)> for Interval<T>
220where
221    T: Numerated + UpperBounded,
222    S: Into<T::Bound>,
223    E: Into<T::Bound>,
224{
225    type Error = TryFromRangeError;
226
227    // NOTE: trying to make upper not inclusive interval `start..=end - 1`
228    fn try_from((start, end): (S, E)) -> Result<Self, Self::Error> {
229        let start: T::Bound = start.into();
230        let end: T::Bound = end.into();
231
232        match (start.unbound(), end.unbound()) {
233            (None, None) => Err(TryFromRangeError::EmptyRange),
234            (None, Some(_)) => Err(TryFromRangeError::IncorrectRange),
235            (start, None) => Self::try_from(start..).map_err(|_| TryFromRangeError::EmptyRange),
236            (Some(start), Some(end)) => Self::try_from_range(start..end),
237        }
238    }
239}
240
241impl<T: Numerated + UpperBounded, I: Into<T::Bound>> TryFrom<Range<I>> for Interval<T> {
242    type Error = TryFromRangeError;
243
244    fn try_from(range: Range<I>) -> Result<Self, Self::Error> {
245        Self::try_from((range.start, range.end))
246    }
247}
248
249/// Trying to make zero len or out of bounds interval.
250#[derive(Debug, Clone, Copy, PartialEq, Eq)]
251pub enum NewWithLenError {
252    /// Trying to make zero len interval.
253    ZeroLen,
254    /// Trying to make out of bounds interval.
255    OutOfBounds,
256}
257
258impl<T: Numerated + UpperBounded> Interval<T> {
259    /// Returns interval `start..=start + len - 1` if it's possible.
260    /// - if `len == None`, then it is supposed, that `len == T::Distance::max_value() + 1`.
261    /// - if `start + len - 1` is out of `T`, then returns [`NewWithLenError::OutOfBounds`].
262    /// - if `len == 0`, then returns [`NewWithLenError::ZeroLen`].
263    pub fn with_len<S: Into<T::Bound>, L: Into<Option<T::Distance>>>(
264        start: S,
265        len: L,
266    ) -> Result<Interval<T>, NewWithLenError> {
267        let start: T::Bound = start.into();
268        let len: Option<T::Distance> = len.into();
269        match (start.unbound(), len) {
270            (_, Some(len)) if len.is_zero() => Err(NewWithLenError::ZeroLen),
271            (None, _) => Err(NewWithLenError::OutOfBounds),
272            (Some(start), len) => {
273                // subtraction `len - 1` is safe, because `len != 0`
274                let distance = len
275                    .map(|len| len - T::Distance::one())
276                    .unwrap_or(T::Distance::max_value());
277                start
278                    .add_if_enclosed_by(distance, T::max_value())
279                    .map(|end| {
280                        debug_assert!(start <= end, "`T: Numerated` impl error");
281                        Self { start, end }
282                    })
283                    .ok_or(NewWithLenError::OutOfBounds)
284            }
285        }
286    }
287}
288
289impl<T: Numerated> Interval<T> {
290    /// - If `self` contains `T::Distance::max_value() + 1` points, then returns [`None`].
291    /// - Else returns `Some(a)`, where `a` is amount of elements in `self`.
292    pub fn raw_len(&self) -> Option<T::Distance> {
293        let (start, end) = self.into_parts();
294        end.distance(start).checked_add(&T::Distance::one())
295    }
296}
297
298impl<T: Numerated + LowerBounded + UpperBounded> Interval<T> {
299    /// Returns `len: T::Distance` (amount of points in `self`) converting it to `T` point:
300    /// - If length is bigger than `T` possible elements amount, then returns `T::Bound` __upper__ value.
301    /// - Else returns as length corresponding `p: T::Bound`:
302    /// ```text
303    ///   { 1 -> T::Bound::from(T::min_value() + 1), 2 -> T::Bound::from(T::min_value() + 2), ... }
304    /// ```
305    pub fn len(&self) -> T::Bound {
306        self.raw_len()
307            .and_then(|raw_len| T::min_value().add_if_enclosed_by(raw_len, T::max_value()))
308            .into()
309    }
310}
311
312impl<T: Debug> Debug for Interval<T> {
313    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
314        write!(f, "{:?}..={:?}", self.start, self.end)
315    }
316}
317
318#[cfg(test)]
319mod tests {
320    use super::*;
321
322    #[test]
323    fn len() {
324        assert_eq!(Interval::<u8>::try_from(1..7).unwrap().len(), 6);
325        assert_eq!(Interval::<u8>::try_from(..1).unwrap().len(), 1);
326        assert_eq!(Interval::<u8>::from(..=1).len(), 2);
327        assert_eq!(Interval::<u8>::try_from(1..).unwrap().len(), 255);
328        assert_eq!(Interval::<u8>::try_from(0..).unwrap().len(), None);
329        assert_eq!(Interval::<u8>::from(..).len(), None);
330
331        assert_eq!(Interval::<u8>::try_from(1..7).unwrap().raw_len(), Some(6));
332        assert_eq!(Interval::<u8>::try_from(..1).unwrap().raw_len(), Some(1));
333        assert_eq!(Interval::<u8>::from(..=1).raw_len(), Some(2));
334        assert_eq!(Interval::<u8>::try_from(1..).unwrap().raw_len(), Some(255));
335        assert_eq!(Interval::<u8>::try_from(0..).unwrap().raw_len(), None);
336        assert_eq!(Interval::<u8>::from(..).raw_len(), None);
337
338        assert_eq!(Interval::<i8>::try_from(-1..1).unwrap().len(), -126); // corresponds to 2 numeration
339        assert_eq!(Interval::<i8>::from(..=1).len(), 2); // corresponds to 130 numeration
340        assert_eq!(Interval::<i8>::try_from(..1).unwrap().len(), 1); // corresponds to 129 numeration
341        assert_eq!(Interval::<i8>::try_from(1..).unwrap().len(), -1); // corresponds to 127 numeration
342        assert_eq!(Interval::<i8>::from(..).len(), None); // corresponds to 256 numeration
343
344        assert_eq!(Interval::<i8>::try_from(-1..1).unwrap().raw_len(), Some(2));
345        assert_eq!(Interval::<i8>::try_from(..1).unwrap().raw_len(), Some(129));
346        assert_eq!(Interval::<i8>::from(..=1).raw_len(), Some(130));
347        assert_eq!(Interval::<i8>::try_from(1..).unwrap().raw_len(), Some(127));
348        assert_eq!(Interval::<i8>::from(..).raw_len(), None);
349    }
350
351    #[test]
352    fn count_from() {
353        assert_eq!(Interval::<u8>::with_len(0, 100).unwrap(), 0..=99);
354        assert_eq!(Interval::<u8>::with_len(0, 255).unwrap(), 0..=254);
355        assert_eq!(Interval::<u8>::with_len(0, None).unwrap(), 0..=255);
356        assert_eq!(Interval::<u8>::with_len(1, 255).unwrap(), 1..=255);
357        assert_eq!(Interval::<u8>::with_len(0, 1).unwrap(), 0..=0);
358    }
359}