Skip to main content

thrust/
intervals.rs

1use std::cmp::min;
2use std::fmt;
3use std::fmt::Display;
4use std::iter::Sum;
5use std::ops::{Add, BitAnd, Sub};
6
7/// A closed interval [start, stop] with inclusive bounds.
8///
9/// Represents a continuous range of values from `start` to `stop`, where both endpoints are included.
10/// Useful for representing time windows, altitude bands, or coordinate ranges in flight planning.
11///
12/// # Generic Parameters
13/// - `T`: Any type implementing `Ord` and numeric operations (e.g., `i32`, `DateTime`, `f64`)
14///
15/// # Fields
16/// - `start`: Inclusive start of the interval
17/// - `stop`: Inclusive end of the interval
18///
19/// # Operations
20/// - **Union (`+`)**: Combine overlapping or adjacent intervals
21/// - **Difference (`-`)**: Subtract an interval (creates gaps)
22/// - **Intersection (`&`)**: Find overlapping regions
23///
24/// # Examples
25/// ```ignore
26/// use thrust::intervals::Interval;
27///
28/// let flight_level_1 = Interval { start: 250, stop: 350 };  // FL250–FL350
29/// let flight_level_2 = Interval { start: 350, stop: 450 };  // FL350–FL450
30///
31/// // Union: creates a single merged interval if adjacent
32/// let merged = &flight_level_1 + &flight_level_2;
33///
34/// // Difference: removes the intersection
35/// let remainder = flight_level_1 - Interval { start: 300, stop: 400 };
36/// ```
37#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
38pub struct Interval<T> {
39    pub start: T,
40    pub stop: T,
41}
42
43impl<T> Display for &Interval<T>
44where
45    T: Display,
46{
47    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48        write!(f, "[{}, {}]", self.start, self.stop)
49    }
50}
51
52/// A collection of disjoint intervals (gaps allowed).
53///
54/// Represents a union of non-overlapping intervals, which can have gaps between them.
55/// This is the result of combining, intersecting, or subtracting intervals.
56/// The collection is typically kept in sorted order for efficient operations.
57///
58/// # Fields
59/// - `elts`: Vector of disjoint intervals in order
60///
61/// # Examples
62/// ```ignore
63/// use thrust::intervals::Interval;
64///
65/// // Two non-adjacent flight level bands (with gap)
66/// let band1 = Interval { start: 250, stop: 350 };
67/// let band2 = Interval { start: 450, stop: 550 };
68/// let collection = band1 + band2;  // Results in IntervalCollection with gap [350–450]
69///
70/// // Calculate total time covered
71/// let total = collection.total_duration();
72/// ```
73#[derive(Debug)]
74pub struct IntervalCollection<T> {
75    pub elts: Vec<Interval<T>>,
76}
77
78impl<T> Display for &IntervalCollection<T>
79where
80    T: Display,
81{
82    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83        write!(f, "[")?;
84        for (i, elt) in self.elts.iter().enumerate() {
85            if i > 0 {
86                write!(f, ", ")?;
87            }
88            write!(f, "{elt}")?;
89        }
90        write!(f, "]")
91    }
92}
93
94impl<T> Add for &Interval<T>
95where
96    T: Ord + Copy,
97{
98    type Output = IntervalCollection<T>;
99    fn add(self, other: &Interval<T>) -> IntervalCollection<T> {
100        let left = IntervalCollection { elts: vec![*self] };
101        let right = IntervalCollection { elts: vec![*other] };
102        &left + &right
103    }
104}
105
106impl<T> Add for Interval<T>
107where
108    T: Ord + Copy,
109{
110    type Output = IntervalCollection<T>;
111    fn add(self, other: Interval<T>) -> IntervalCollection<T> {
112        let left = IntervalCollection { elts: vec![self] };
113        let right = IntervalCollection { elts: vec![other] };
114        &left + &right
115    }
116}
117
118impl<T> Add<IntervalCollection<T>> for &Interval<T>
119where
120    T: Ord + Copy,
121{
122    type Output = IntervalCollection<T>;
123    fn add(self, other: IntervalCollection<T>) -> IntervalCollection<T> {
124        let left = IntervalCollection { elts: vec![*self] };
125        &left + &other
126    }
127}
128
129impl<T> Add<&IntervalCollection<T>> for &Interval<T>
130where
131    T: Ord + Copy,
132{
133    type Output = IntervalCollection<T>;
134    fn add(self, other: &IntervalCollection<T>) -> IntervalCollection<T> {
135        let left = IntervalCollection { elts: vec![*self] };
136        &left + other
137    }
138}
139
140impl<T> Add<&Interval<T>> for &IntervalCollection<T>
141where
142    T: Ord + Copy,
143{
144    type Output = IntervalCollection<T>;
145    fn add(self, other: &Interval<T>) -> IntervalCollection<T> {
146        let right = IntervalCollection { elts: vec![*other] };
147        self + &right
148    }
149}
150
151impl<T> Add<Interval<T>> for IntervalCollection<T>
152where
153    T: Ord + Copy,
154{
155    type Output = IntervalCollection<T>;
156    fn add(self, other: Interval<T>) -> IntervalCollection<T> {
157        let right = IntervalCollection { elts: vec![other] };
158        self + right
159    }
160}
161
162impl<T> Add<&Interval<T>> for IntervalCollection<T>
163where
164    T: Ord + Copy,
165{
166    type Output = IntervalCollection<T>;
167    fn add(self, other: &Interval<T>) -> IntervalCollection<T> {
168        let right = IntervalCollection { elts: vec![*other] };
169        self + right
170    }
171}
172
173impl<T> Add for &IntervalCollection<T>
174where
175    T: Ord + Copy,
176{
177    type Output = IntervalCollection<T>;
178    fn add(self, other: &IntervalCollection<T>) -> IntervalCollection<T> {
179        let mut elts = Vec::new();
180        let mut start = min(&self.elts[0], &other.elts[0]);
181
182        loop {
183            let swiping_line = start.start;
184            let mut horizon = start.stop;
185
186            horizon = self
187                .elts
188                .iter()
189                .chain(other.elts.iter())
190                .filter(|elt| swiping_line <= elt.start && elt.start <= horizon)
191                .map(|elt| elt.stop)
192                .max()
193                .expect("Unexpected error");
194
195            loop {
196                match self
197                    .elts
198                    .iter()
199                    .chain(other.elts.iter())
200                    .filter(|elt| elt.start <= horizon && horizon < elt.stop)
201                    .map(|elt| elt.stop)
202                    .max()
203                {
204                    None => break,
205                    Some(x) => horizon = x,
206                }
207            }
208            elts.push(Interval {
209                start: swiping_line,
210                stop: horizon,
211            });
212            match self
213                .elts
214                .iter()
215                .chain(other.elts.iter())
216                .filter(|elt| elt.start > horizon)
217                .min()
218            {
219                None => break,
220                Some(x) => start = x,
221            }
222        }
223
224        IntervalCollection { elts }
225    }
226}
227
228impl<T> Add for IntervalCollection<T>
229where
230    T: Ord + Copy,
231{
232    type Output = IntervalCollection<T>;
233    fn add(self, other: IntervalCollection<T>) -> IntervalCollection<T> {
234        &self + &other
235    }
236}
237impl<T, Delta> Sub for Interval<T>
238where
239    T: Sub<T, Output = Delta> + Add<Delta, Output = T> + Copy + PartialOrd,
240    Delta: Copy,
241{
242    type Output = IntervalCollection<T>;
243    fn sub(self, other: Interval<T>) -> IntervalCollection<T> {
244        let mut elts = Vec::new();
245        if self.overlap(&other) {
246            if other.start > self.start {
247                elts.push(Interval {
248                    start: self.start,
249                    stop: other.start,
250                })
251            }
252            if other.stop < self.stop {
253                elts.push(Interval {
254                    start: other.stop,
255                    stop: self.stop,
256                })
257            }
258        } else {
259            elts.push(self)
260        }
261        IntervalCollection { elts }
262    }
263}
264
265impl<T, Delta> Sub<Interval<T>> for IntervalCollection<T>
266where
267    T: Sub<T, Output = Delta> + Add<Delta, Output = T> + Copy + PartialOrd,
268    Delta: Copy,
269{
270    type Output = IntervalCollection<T>;
271    fn sub(self, other: Interval<T>) -> IntervalCollection<T> {
272        let mut elts = Vec::new();
273        for elt in self.elts {
274            if elt.overlap(&other) {
275                if other.start > elt.start {
276                    elts.push(Interval {
277                        start: elt.start,
278                        stop: other.start,
279                    })
280                }
281                if other.stop < elt.stop {
282                    elts.push(Interval {
283                        start: other.stop,
284                        stop: elt.stop,
285                    })
286                }
287            } else {
288                elts.push(elt)
289            }
290        }
291        IntervalCollection { elts }
292    }
293}
294
295impl<T, Delta> Sub for IntervalCollection<T>
296where
297    T: Sub<T, Output = Delta> + Add<Delta, Output = T> + Copy + PartialOrd,
298    Delta: Copy,
299{
300    type Output = IntervalCollection<T>;
301    fn sub(self, other: IntervalCollection<T>) -> IntervalCollection<T> {
302        let mut res = self;
303        for elt in other.elts {
304            res = res - elt;
305        }
306        res
307    }
308}
309
310/* Implement intersection between two Intervals */
311impl<T> BitAnd for &Interval<T>
312where
313    T: Copy + Clone + PartialEq + PartialOrd,
314{
315    type Output = Option<Interval<T>>;
316    fn bitand(self, other: &Interval<T>) -> Option<Interval<T>> {
317        match self.overlap(other) {
318            true => Some(Interval {
319                start: match self.start > other.start {
320                    true => self.start,
321                    false => other.start,
322                },
323                stop: match self.stop < other.stop {
324                    true => self.stop,
325                    false => other.stop,
326                },
327            }),
328            false => None,
329        }
330    }
331}
332impl<T> BitAnd<&IntervalCollection<T>> for &Interval<T>
333where
334    T: Copy + Clone + PartialEq + PartialOrd,
335{
336    type Output = IntervalCollection<T>;
337    fn bitand(self, other: &IntervalCollection<T>) -> IntervalCollection<T> {
338        let mut elts = Vec::<Interval<T>>::with_capacity(other.elts.len());
339        for interval in &other.elts {
340            match self & interval {
341                None => (),
342                Some(i) => elts.push(i),
343            }
344        }
345        IntervalCollection { elts }
346    }
347}
348
349impl<T> BitAnd<&Interval<T>> for &IntervalCollection<T>
350where
351    T: Copy + Clone + PartialEq + PartialOrd,
352{
353    type Output = IntervalCollection<T>;
354    fn bitand(self, other: &Interval<T>) -> IntervalCollection<T> {
355        other & self
356    }
357}
358
359impl<T> BitAnd for &IntervalCollection<T>
360where
361    T: Copy + Clone + PartialEq + PartialOrd,
362{
363    type Output = IntervalCollection<T>;
364    fn bitand(self, other: &IntervalCollection<T>) -> IntervalCollection<T> {
365        let mut elts = Vec::<Interval<T>>::with_capacity(self.elts.len());
366        for interval in &other.elts {
367            let r = self & interval;
368            elts.extend(r.elts)
369        }
370        IntervalCollection { elts }
371    }
372}
373
374impl<T, Delta> Interval<T>
375where
376    T: Sub<T, Output = Delta> + Add<Delta, Output = T> + Copy,
377    Delta: Copy,
378{
379    pub fn duration(self) -> Delta {
380        self.stop - self.start
381    }
382    pub fn shift(&self, delta: Delta) -> Interval<T> {
383        Interval {
384            start: self.start + delta,
385            stop: self.stop + delta,
386        }
387    }
388}
389
390impl<T> Interval<T>
391where
392    T: PartialOrd,
393{
394    pub fn overlap(&self, other: &Interval<T>) -> bool {
395        self.start < other.stop && self.stop > other.start
396    }
397}
398
399impl<T, Delta> IntervalCollection<T>
400where
401    T: Sub<T, Output = Delta> + Add<Delta, Output = T> + Copy + PartialOrd,
402    Delta: Copy + Sum,
403{
404    pub fn total_duration(&self) -> Delta {
405        self.elts.iter().map(|elt| elt.duration()).sum()
406    }
407}
408
409#[cfg(test)]
410mod tests {
411
412    use super::Interval;
413    use jiff::{Timestamp, ToSpan};
414
415    static I1: Interval<i32> = Interval { start: 0, stop: 1 };
416    static I2: Interval<i32> = Interval { start: 1, stop: 2 };
417    static I3: Interval<i32> = Interval { start: 2, stop: 3 };
418    static I4: Interval<i32> = Interval { start: 3, stop: 4 };
419    static I5: Interval<i32> = Interval { start: 4, stop: 5 };
420
421    #[test]
422    fn interval_i32() {
423        assert_eq!(I1.duration(), 1);
424        let shifted = I1.shift(1);
425        assert_eq!(shifted.duration(), 1);
426        assert_ne!(shifted, I1);
427        assert_eq!(shifted, I2);
428        assert_eq!(format!("{:?}", &shifted), "Interval { start: 1, stop: 2 }");
429        assert_eq!(format!("{:}", &shifted), "[1, 2]");
430    }
431
432    #[test]
433    fn interval_dt() {
434        let i_dt: Interval<Timestamp> = Interval {
435            start: "2024-01-20T12:00:00Z".parse().expect("error date"),
436            stop: "2024-01-20T13:00:00Z".parse().expect("error date"),
437        };
438        assert_eq!(i_dt.duration().compare(1.hour()).unwrap(), std::cmp::Ordering::Equal);
439        assert_eq!(
440            i_dt.shift(5.hour()).duration().compare(1.hour()).unwrap(),
441            std::cmp::Ordering::Equal
442        );
443    }
444    #[test]
445    fn intervals_consistent() {
446        assert_eq!(
447            format!("{:?}", I1 + I2),
448            "IntervalCollection { elts: [Interval { start: 0, stop: 2 }] }"
449        );
450        assert_eq!(format!("{:}", &(I1 + I2)), "[[0, 2]]");
451        assert_eq!(format!("{:}", &(I1 + I3)), "[[0, 1], [2, 3]]");
452        assert_eq!(format!("{:}", &(I2 + I4)), "[[1, 2], [3, 4]]");
453        let s1 = (I1 + I3) + (I2 + I4);
454        assert_eq!(format!("{:}", &s1), "[[0, 4]]");
455        let s2 = (I1 + I3) + (I4 + I5);
456        assert_eq!(format!("{:}", &s2), "[[0, 1], [2, 5]]");
457        let s3 = I1 + I3 + I4 + I5;
458        assert_eq!(format!("{:}", &s3), "[[0, 1], [2, 5]]");
459
460        let i1: Interval<Timestamp> = Interval {
461            start: "2024-01-20T12:00:00Z".parse().expect("error date"),
462            stop: "2024-01-20T13:00:00Z".parse().expect("error date"),
463        };
464        let i2 = Interval {
465            start: "2024-01-20T13:00:00Z".parse().expect("error date"),
466            stop: "2024-01-20T14:00:00Z".parse().expect("error date"),
467        };
468        assert_eq!(
469            format!("{:}", &(i1 + i2)),
470            "[[2024-01-20T12:00:00Z, 2024-01-20T14:00:00Z]]"
471        );
472    }
473
474    #[test]
475    fn intervals_sub() {
476        assert_eq!(format!("{:}", &(I1 - I2)), "[[0, 1]]");
477        assert_eq!(format!("{:}", &(Interval { start: 0, stop: 2 } - I2)), "[[0, 1]]");
478        assert_eq!(format!("{:}", &((I1 + I2 + I3) - I2)), "[[0, 1], [2, 3]]");
479        assert_eq!(format!("{:}", &((I1 + I2) - (I3 + I2))), "[[0, 1]]");
480        assert_eq!(
481            format!("{:}", &(((I1 + I2) + (I2 + I3) + I5) - (I2 + I3))),
482            "[[0, 1], [4, 5]]"
483        );
484    }
485}