1
//! This module exposes helpers for working with types that implement
2
//! [`RangeBounds`].
3

            
4
use std::cmp::{self, Ord};
5
use std::ops::{Bound, RangeBounds};
6

            
7
/// An extension trait for [`RangeBounds`].
8
pub trait RangeBoundsExt<T>: RangeBounds<T> {
9
    /// Compute the intersection of two `RangeBound`s.
10
    ///
11
    /// In essence, this computes the intersection of the intervals described by bounds of the
12
    /// two objects.
13
    ///
14
    /// Returns `None` if the intersection of the two ranges is the empty set.
15
    fn intersect<'a, U: RangeBounds<T>>(
16
        &'a self,
17
        other: &'a U,
18
    ) -> Option<(Bound<&'a T>, Bound<&'a T>)>;
19
}
20

            
21
impl<T, R> RangeBoundsExt<T> for R
22
where
23
    R: RangeBounds<T>,
24
    T: Ord,
25
{
26
40170
    fn intersect<'a, U: RangeBounds<T>>(
27
40170
        &'a self,
28
40170
        other: &'a U,
29
40170
    ) -> Option<(Bound<&'a T>, Bound<&'a T>)> {
30
        use Bound::*;
31

            
32
40170
        let this_start = self.start_bound();
33
40170
        let other_start = other.start_bound();
34
40170
        let this_end = self.end_bound();
35
40170
        let other_end = other.end_bound();
36
40170

            
37
40170
        let start = bounds_max(this_start, other_start);
38
40170
        let end = bounds_min(this_end, other_end);
39
40170

            
40
40170
        match (start, end) {
41
10176
            (Excluded(start), Excluded(end)) | (Included(start), Excluded(end)) if start == end => {
42
                // The interval (n, n) = [n, n) = {} (empty set).
43
1440
                None
44
            }
45
7268
            (Included(start), Included(end))
46
8216
            | (Included(start), Excluded(end))
47
7840
            | (Excluded(start), Included(end))
48
8548
            | (Excluded(start), Excluded(end))
49
8992
                if start > end =>
50
            {
51
                // For any a > b, the intervals [a, b], [a, b), (a, b], (a, b) are empty.
52
31872
                None
53
            }
54
6858
            _ => Some((start, end)),
55
        }
56
40170
    }
57
}
58

            
59
/// Return the largest of `b1` and `b2`.
60
///
61
/// If one of the bounds is [Unbounded](Bound::Unbounded), the other will be returned.
62
40170
fn bounds_max<'a, T: Ord>(b1: Bound<&'a T>, b2: Bound<&'a T>) -> Bound<&'a T> {
63
    use Bound::*;
64

            
65
40170
    match (b1, b2) {
66
10084
        (Included(b1), Included(b2)) => Included(cmp::max(b1, b2)),
67
10180
        (Excluded(b1), Excluded(b2)) => Excluded(cmp::max(b1, b2)),
68

            
69
9882
        (Excluded(b1), Included(b2)) if b1 >= b2 => Excluded(b1),
70
4532
        (Excluded(_), Included(b2)) => Included(b2),
71

            
72
9882
        (Included(b1), Excluded(b2)) if b2 >= b1 => Excluded(b2),
73
4532
        (Included(b1), Excluded(_)) => Included(b1),
74

            
75
142
        (b, Unbounded) | (Unbounded, b) => b,
76
    }
77
40170
}
78

            
79
/// Return the smallest of `b1` and `b2`.
80
///
81
/// If one of the bounds is [Unbounded](Bound::Unbounded), the other will be returned.
82
40170
fn bounds_min<'a, T: Ord>(b1: Bound<&'a T>, b2: Bound<&'a T>) -> Bound<&'a T> {
83
    use Bound::*;
84

            
85
40170
    match (b1, b2) {
86
9826
        (Included(b1), Included(b2)) => Included(cmp::min(b1, b2)),
87
9876
        (Excluded(b1), Excluded(b2)) => Excluded(cmp::min(b1, b2)),
88

            
89
10212
        (Excluded(b1), Included(b2)) if b1 <= b2 => Excluded(b1),
90
4570
        (Excluded(_), Included(b2)) => Included(b2),
91

            
92
10212
        (Included(b1), Excluded(b2)) if b2 <= b1 => Excluded(b2),
93
4570
        (Included(b1), Excluded(_)) => Included(b1),
94

            
95
44
        (b, Unbounded) | (Unbounded, b) => b,
96
    }
97
40170
}
98

            
99
#[cfg(test)]
100
mod test {
101
    // @@ begin test lint list maintained by maint/add_warning @@
102
    #![allow(clippy::bool_assert_comparison)]
103
    #![allow(clippy::clone_on_copy)]
104
    #![allow(clippy::dbg_macro)]
105
    #![allow(clippy::mixed_attributes_style)]
106
    #![allow(clippy::print_stderr)]
107
    #![allow(clippy::print_stdout)]
108
    #![allow(clippy::single_char_pattern)]
109
    #![allow(clippy::unwrap_used)]
110
    #![allow(clippy::unchecked_duration_subtraction)]
111
    #![allow(clippy::useless_vec)]
112
    #![allow(clippy::needless_pass_by_value)]
113
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
114
    use super::*;
115
    use std::fmt::Debug;
116
    use std::time::{Duration, SystemTime};
117
    use Bound::{Excluded as Excl, Included as Incl, Unbounded};
118

            
119
    /// A helper that computes the intersection of `range1` and `range2`.
120
    ///
121
    /// This function also asserts that the intersection operation is commutative.
122
    fn intersect<'a, T, R: RangeBounds<T>>(
123
        range1: &'a R,
124
        range2: &'a R,
125
    ) -> Option<(Bound<&'a T>, Bound<&'a T>)>
126
    where
127
        T: PartialEq + Ord + Debug,
128
    {
129
        let intersection1 = range1.intersect(range2);
130
        let intersection2 = range2.intersect(range1);
131

            
132
        assert_eq!(intersection1, intersection2);
133

            
134
        intersection1
135
    }
136

            
137
    /// A helper for randomly generating either an inclusive or an exclusive bound with a
138
    /// particular value.
139
    fn random_bound<T>(value: T) -> Bound<T> {
140
        if rand::random() {
141
            Bound::Included(value)
142
        } else {
143
            Bound::Excluded(value)
144
        }
145
    }
146

            
147
    #[test]
148
    fn no_overlap() {
149
        #[allow(clippy::type_complexity)]
150
        const NON_OVERLAPPING_RANGES: &[(
151
            (Bound<usize>, Bound<usize>),
152
            (Bound<usize>, Bound<usize>),
153
        )] = &[
154
            // (1, 2) and (3, 4)
155
            ((Excl(1), Excl(2)), (Excl(3), Excl(4))),
156
            // (1, 2) and (2, 3)
157
            ((Excl(1), Excl(2)), (Excl(2), Excl(3))),
158
            // (1, 2) and [2, 3)
159
            ((Excl(1), Excl(2)), (Incl(2), Excl(3))),
160
            // (1, 2) and [2, 3]
161
            ((Excl(1), Excl(2)), (Incl(3), Incl(4))),
162
            // (-inf, 2) and [2, 3]
163
            ((Unbounded, Excl(2)), (Incl(2), Incl(3))),
164
            // (-inf, 2) and (2, inf)
165
            ((Unbounded, Excl(2)), (Excl(2), Unbounded)),
166
            // (-inf, 2) and [2, inf)
167
            ((Unbounded, Excl(2)), (Incl(2), Unbounded)),
168
        ];
169

            
170
        for (range1, range2) in NON_OVERLAPPING_RANGES {
171
            let intersection = intersect(range1, range2);
172
            assert!(
173
                intersection.is_none(),
174
                "{:?} and {:?} => {:?}",
175
                range1,
176
                range2,
177
                intersection
178
            );
179
        }
180
    }
181

            
182
    #[test]
183
    fn intersect_unbounded_start() {
184
        // (-inf, 3)
185
        let range1 = (Unbounded, Excl(3));
186
        // [2, 5]
187
        let range2 = (Incl(2), Incl(5));
188

            
189
        let intersection = intersect(&range1, &range2).unwrap();
190

            
191
        // intersection = [2 3]
192
        assert_eq!(intersection.start_bound(), Bound::Included(&2));
193
        assert_eq!(intersection.end_bound(), Bound::Excluded(&3));
194
    }
195

            
196
    #[test]
197
    fn intersect_unbounded_end() {
198
        // (8, inf)
199
        let range1 = (Excl(8), Unbounded);
200
        // [8, 20]
201
        let range2 = (Incl(8), Incl(20));
202

            
203
        let intersection = intersect(&range1, &range2).unwrap();
204

            
205
        // intersection = (8, 20]
206
        assert_eq!(intersection.start_bound(), Bound::Excluded(&8));
207
        assert_eq!(intersection.end_bound(), Bound::Included(&20));
208
    }
209

            
210
    #[test]
211
    fn intersect_unbounded_range() {
212
        #[allow(clippy::type_complexity)]
213
        const RANGES: &[(Bound<usize>, Bound<usize>)] = &[
214
            // (1, 2)
215
            (Excl(1), Excl(2)),
216
            // (1, 2]
217
            (Excl(1), Incl(2)),
218
            // [1, 2]
219
            (Incl(1), Incl(2)),
220
            // [1, 2)
221
            (Incl(1), Excl(2)),
222
            // (1, inf)
223
            (Excl(1), Unbounded),
224
            // [1, inf)
225
            (Incl(1), Unbounded),
226
            // (-inf, 2)
227
            (Unbounded, Excl(2)),
228
            // (-inf, 2]
229
            (Unbounded, Incl(2)),
230
        ];
231

            
232
        // The intersection of any interval I with (Unbounded, Unbounded) will be I.
233
        let range1 = (Unbounded, Unbounded);
234

            
235
        for range2 in RANGES {
236
            let range2 = (range2.0.as_ref(), range2.1.as_ref());
237
            assert_eq!(intersect(&range1, &range2).unwrap(), range2);
238
        }
239
    }
240

            
241
    #[test]
242
    fn intersect_time_bounds() {
243
        const MIN: Duration = Duration::from_secs(60);
244

            
245
        // time (relative to now):  0   1   2   3
246
        //                          |   |   |   |
247
        // [t1, t2]:                [.......]
248
        // [t3, t4]:                    [.......]
249
        // intersection:                [...]
250
        let now = SystemTime::now();
251
        let t1 = now;
252
        let t2 = now + 2 * MIN;
253

            
254
        let t3 = now + 1 * MIN;
255
        let t4 = now + 3 * MIN;
256

            
257
        let b1 = (Bound::Included(t1), Bound::Included(t2));
258
        let b2 = (Bound::Included(t3), Bound::Included(t4));
259
        let expected = (Bound::Included(&t3), Bound::Included(&t2));
260
        assert_eq!(intersect(&b1, &b2).unwrap(), expected);
261

            
262
        //  t1  -  -  t2  -  -
263
        //                   t3  -  -  t4
264
        //
265
        // time (relative to now):  0   1   2   3   4   5   6   7
266
        //                          |   |   |   |   |   |   |   |
267
        // [t1, t2]:                [.......]
268
        // [t3, t4]:                                [............]
269
        let t3 = now + 4 * MIN;
270
        let t4 = now + 7 * MIN;
271
        let b2 = (Bound::Included(t3), Bound::Included(t4));
272
        assert!(intersect(&b1, &b2).is_none());
273
    }
274

            
275
    #[test]
276
    fn combinatorial() {
277
        for i in 0..10 {
278
            for j in 0..10 {
279
                for k in 0..10 {
280
                    for l in 0..10 {
281
                        let range1 = (random_bound(i), random_bound(j));
282
                        let range2 = (random_bound(k), random_bound(l));
283

            
284
                        let intersection = intersect(&range1, &range2);
285

            
286
                        for witness in 0..10 {
287
                            let c1 = range1.contains(&witness);
288
                            let c2 = range2.contains(&witness);
289
                            let both_contain_witness = c1 && c2;
290

            
291
                            if both_contain_witness {
292
                                // If both ranges contain `witness` they definitely intersect.
293
                                assert!(intersection.unwrap().contains(&witness));
294
                            } else if let Some(intersection) = intersection {
295
                                // If one of them doesn't contain `witness`, `witness` is
296
                                // definitely not part of the intersection.
297
                                assert!(!intersection.contains(&witness));
298
                            }
299
                        }
300
                    }
301
                }
302
            }
303
        }
304
    }
305
}