my_interval/
interval.rs

1use crate::bound_point::BoundPoint;
2
3#[derive(Debug, Clone, Copy, PartialEq)]
4pub enum IntervalType {
5    Open,
6    StartOpen,
7    EndOpen,
8    Close,
9}
10
11#[derive(Debug, Clone, Copy, PartialEq)]
12pub struct Interval<T>
13where
14    T: Ord,
15{
16    start: BoundPoint<T>,
17    end: BoundPoint<T>,
18}
19
20#[derive(Debug, Clone, Copy, PartialEq)]
21pub enum IntervalError {
22    StartMustBeMinorThanEnd,
23}
24
25impl<T: Ord> Interval<T> {
26    pub fn from_to(start: T, end: T, interval_type: IntervalType) -> Result<Self, IntervalError> {
27        Self::validate(&start, &end)?;
28        match interval_type {
29            IntervalType::Open => Ok(Interval {
30                start: BoundPoint::after(start),
31                end: BoundPoint::before(end),
32            }),
33            IntervalType::StartOpen => Ok(Interval {
34                start: BoundPoint::after(start),
35                end: BoundPoint::at(end),
36            }),
37            IntervalType::EndOpen => Ok(Interval {
38                start: BoundPoint::at(start),
39                end: BoundPoint::before(end),
40            }),
41            IntervalType::Close => Ok(Interval {
42                start: BoundPoint::at(start),
43                end: BoundPoint::at(end),
44            }),
45        }
46    }
47
48    pub fn since_exclusive(value: T) -> Self {
49        Interval {
50            start: BoundPoint::after(value),
51            end: BoundPoint::pos_infinity(),
52        }
53    }
54
55    pub fn since_inclusive(value: T) -> Self {
56        Interval {
57            start: BoundPoint::at(value),
58            end: BoundPoint::pos_infinity(),
59        }
60    }
61
62    pub fn until_exclusive(value: T) -> Self {
63        Interval {
64            start: BoundPoint::neg_infinity(),
65            end: BoundPoint::before(value),
66        }
67    }
68
69    pub fn until_inclusive(value: T) -> Self {
70        Interval {
71            start: BoundPoint::neg_infinity(),
72            end: BoundPoint::at(value),
73        }
74    }
75
76    fn validate(start: &T, end: &T) -> Result<(), IntervalError> {
77        if start > end {
78            Err(IntervalError::StartMustBeMinorThanEnd)
79        } else {
80            Ok(())
81        }
82    }
83
84    pub fn contains(&self, value: T) -> bool {
85        let bound_point = BoundPoint::at(value);
86        self.start <= bound_point && self.end >= bound_point
87    }
88
89    pub fn overlaps(&self, other: &Interval<T>) -> bool {
90        self.start <= other.end && self.end >= other.start
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97    use rstest::rstest;
98
99    #[rstest]
100    // from to
101    #[case(Interval::from_to(1, 3, IntervalType::Open).unwrap(), 1,  false)]
102    #[case(Interval::from_to(1, 3, IntervalType::Open).unwrap(), 3,  false)]
103    #[case(Interval::from_to(1, 3, IntervalType::Open).unwrap(), 2,  true)]
104    #[case(Interval::from_to(1, 3, IntervalType::StartOpen).unwrap(), 1,  false)]
105    #[case(Interval::from_to(1, 3, IntervalType::StartOpen).unwrap(), 3,  true)]
106    #[case(Interval::from_to(1, 3, IntervalType::StartOpen).unwrap(), 2,  true)]
107    #[case(Interval::from_to(1, 3, IntervalType::EndOpen).unwrap(), 1,  true)]
108    #[case(Interval::from_to(1, 3, IntervalType::EndOpen).unwrap(), 3,  false)]
109    #[case(Interval::from_to(1, 3, IntervalType::EndOpen).unwrap(), 2,  true)]
110    #[case(Interval::from_to(1, 3, IntervalType::Close).unwrap(), 1,  true)]
111    #[case(Interval::from_to(1, 3, IntervalType::Close).unwrap(), 3,  true)]
112    #[case(Interval::from_to(1, 3, IntervalType::Close).unwrap(), 2,  true)]
113    #[case(Interval::from_to(1, 3, IntervalType::Close).unwrap(), 0,  false)]
114    #[case(Interval::from_to(1, 3, IntervalType::Close).unwrap(), 4,  false)]
115    // until
116    #[case(Interval::until_exclusive(1), 0,  true)]
117    #[case(Interval::until_exclusive(1), 1,  false)]
118    #[case(Interval::until_exclusive(1), 2,  false)]
119    #[case(Interval::until_inclusive(1), 0,  true)]
120    #[case(Interval::until_inclusive(1), 1,  true)]
121    #[case(Interval::until_inclusive(1), 2,  false)]
122    // since
123    #[case(Interval::since_exclusive(1), 0,  false)]
124    #[case(Interval::since_exclusive(1), 1,  false)]
125    #[case(Interval::since_exclusive(1), 2,  true)]
126    #[case(Interval::since_inclusive(1), 0,  false)]
127    #[case(Interval::since_inclusive(1), 1,  true)]
128    #[case(Interval::since_inclusive(1), 2,  true)]
129    fn test_contains(#[case] interval: Interval<i32>, #[case] value: i32, #[case] expected: bool) {
130        let actual = interval.contains(value);
131        assert_eq!(
132            actual, expected,
133            "failed: {:?}, {} → got {}, expected {}",
134            interval, value, expected, actual
135        );
136    }
137
138    #[rstest]
139    // 1. Boundary-type combinations:
140    //    - Both Open
141    //    - One Close, another Open
142    //    - Both Close
143    //
144    // 2. Positional relationships between the two intervals:
145    //    - identical open intervals overlap
146    //    - one interval completely inside another
147    //    - touch at one start point
148    //    - completely disjoint
149    //    - touch at one end point
150    //    - partial overlap inside
151
152    // Both Open
153    #[case(Interval::from_to(0, 3, IntervalType::Open).unwrap(), Interval::from_to(0, 3, IntervalType::Open).unwrap(),  true)]
154    #[case(Interval::from_to(0, 3, IntervalType::Open).unwrap(), Interval::from_to(1, 2, IntervalType::Open).unwrap(),  true)]
155    #[case(Interval::from_to(0, 3, IntervalType::Open).unwrap(), Interval::from_to(-1, 0, IntervalType::Open).unwrap(),  false)]
156    #[case(Interval::from_to(0, 3, IntervalType::Open).unwrap(), Interval::from_to(-2, -1, IntervalType::Open).unwrap(),  false)]
157    #[case(Interval::from_to(0, 3, IntervalType::Open).unwrap(), Interval::from_to(3, 4, IntervalType::Open).unwrap(),  false)]
158    #[case(Interval::from_to(0, 3, IntervalType::Open).unwrap(), Interval::from_to(-1, 2, IntervalType::Open).unwrap(),  true)]
159    // One Close. Another Open
160    #[case(Interval::from_to(0, 3, IntervalType::Close).unwrap(), Interval::from_to(0, 3, IntervalType::Open).unwrap(),  true)]
161    #[case(Interval::from_to(0, 3, IntervalType::Close).unwrap(), Interval::from_to(1, 2, IntervalType::Open).unwrap(),  true)]
162    #[case(Interval::from_to(0, 3, IntervalType::Close).unwrap(), Interval::from_to(-1, 0, IntervalType::Open).unwrap(),  false)]
163    #[case(Interval::from_to(0, 3, IntervalType::Close).unwrap(), Interval::from_to(-2, -1, IntervalType::Open).unwrap(),  false)]
164    #[case(Interval::from_to(0, 3, IntervalType::Close).unwrap(), Interval::from_to(3, 4, IntervalType::Open).unwrap(),  false)]
165    #[case(Interval::from_to(0, 3, IntervalType::Close).unwrap(), Interval::from_to(-1, 2, IntervalType::Open).unwrap(),  true)]
166    // Both Close
167    #[case(Interval::from_to(0, 3, IntervalType::Close).unwrap(), Interval::from_to(0, 3, IntervalType::Close).unwrap(),  true)]
168    #[case(Interval::from_to(0, 3, IntervalType::Close).unwrap(), Interval::from_to(1, 2, IntervalType::Close).unwrap(),  true)]
169    #[case(Interval::from_to(0, 3, IntervalType::Close).unwrap(), Interval::from_to(-1, 0, IntervalType::Close).unwrap(),  true)]
170    #[case(Interval::from_to(0, 3, IntervalType::Close).unwrap(), Interval::from_to(-2, -1, IntervalType::Close).unwrap(),  false)]
171    #[case(Interval::from_to(0, 3, IntervalType::Close).unwrap(), Interval::from_to(3, 4, IntervalType::Close).unwrap(),  true)]
172    #[case(Interval::from_to(0, 3, IntervalType::Close).unwrap(), Interval::from_to(-1, 2, IntervalType::Close).unwrap(),  true)]
173    fn test_overlaps(
174        #[case] interval: Interval<i32>,
175        #[case] other: Interval<i32>,
176        #[case] expected: bool,
177    ) {
178        let actual = interval.overlaps(&other);
179        assert_eq!(
180            actual, expected,
181            "failed: {:?}, {:?} → got {}, expected {}",
182            interval, other, expected, actual
183        );
184    }
185}