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: Ord>: 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
194551
    fn intersect<'a, U: RangeBounds<T>>(
16
194551
        &'a self,
17
194551
        other: &'a U,
18
194551
    ) -> Option<(Bound<&'a T>, Bound<&'a T>)> {
19
        use Bound::*;
20

            
21
194551
        let this_start = self.start_bound();
22
194551
        let other_start = other.start_bound();
23
194551
        let this_end = self.end_bound();
24
194551
        let other_end = other.end_bound();
25

            
26
194551
        let start = bounds_max(this_start, other_start);
27
194551
        let end = bounds_min(this_end, other_end);
28

            
29
194551
        match (start, end) {
30
10152
            (Excluded(start), Excluded(end)) | (Included(start), Excluded(end)) if start == end => {
31
                // The interval (n, n) = [n, n) = {} (empty set).
32
1524
                None
33
            }
34
37702
            (Included(start), Included(end))
35
8064
            | (Included(start), Excluded(end))
36
7712
            | (Excluded(start), Included(end))
37
8864
            | (Excluded(start), Excluded(end))
38
162797
                if start > end =>
39
            {
40
                // For any a > b, the intervals [a, b], [a, b), (a, b], (a, b) are empty.
41
62342
                None
42
            }
43
130685
            _ => Some((start, end)),
44
        }
45
194551
    }
46
}
47

            
48
impl<T: Ord, R: RangeBounds<T>> RangeBoundsExt<T> for R {}
49

            
50
/// Return the largest of `b1` and `b2`.
51
///
52
/// If one of the bounds is [Unbounded](Bound::Unbounded), the other will be returned.
53
194551
fn bounds_max<'a, T: Ord>(b1: Bound<&'a T>, b2: Bound<&'a T>) -> Bound<&'a T> {
54
    use Bound::*;
55

            
56
194551
    match (b1, b2) {
57
157615
        (Included(b1), Included(b2)) => Included(cmp::max(b1, b2)),
58
9892
        (Excluded(b1), Excluded(b2)) => Excluded(cmp::max(b1, b2)),
59

            
60
10186
        (Excluded(b1), Included(b2)) if b1 >= b2 => Excluded(b1),
61
4632
        (Excluded(_), Included(b2)) => Included(b2),
62

            
63
10186
        (Included(b1), Excluded(b2)) if b2 >= b1 => Excluded(b2),
64
4632
        (Included(b1), Excluded(_)) => Included(b1),
65

            
66
6672
        (b, Unbounded) | (Unbounded, b) => b,
67
    }
68
194551
}
69

            
70
/// Return the smallest of `b1` and `b2`.
71
///
72
/// If one of the bounds is [Unbounded](Bound::Unbounded), the other will be returned.
73
194551
fn bounds_min<'a, T: Ord>(b1: Bound<&'a T>, b2: Bound<&'a T>) -> Bound<&'a T> {
74
    use Bound::*;
75

            
76
194551
    match (b1, b2) {
77
157475
        (Included(b1), Included(b2)) => Included(cmp::min(b1, b2)),
78
10244
        (Excluded(b1), Excluded(b2)) => Excluded(cmp::min(b1, b2)),
79

            
80
10082
        (Excluded(b1), Included(b2)) if b1 <= b2 => Excluded(b1),
81
4478
        (Excluded(_), Included(b2)) => Included(b2),
82

            
83
10082
        (Included(b1), Excluded(b2)) if b2 <= b1 => Excluded(b2),
84
4478
        (Included(b1), Excluded(_)) => Included(b1),
85

            
86
6668
        (b, Unbounded) | (Unbounded, b) => b,
87
    }
88
194551
}
89

            
90
#[cfg(test)]
91
mod test {
92
    // @@ begin test lint list maintained by maint/add_warning @@
93
    #![allow(clippy::bool_assert_comparison)]
94
    #![allow(clippy::clone_on_copy)]
95
    #![allow(clippy::dbg_macro)]
96
    #![allow(clippy::mixed_attributes_style)]
97
    #![allow(clippy::print_stderr)]
98
    #![allow(clippy::print_stdout)]
99
    #![allow(clippy::single_char_pattern)]
100
    #![allow(clippy::unwrap_used)]
101
    #![allow(clippy::unchecked_time_subtraction)]
102
    #![allow(clippy::useless_vec)]
103
    #![allow(clippy::needless_pass_by_value)]
104
    #![allow(clippy::string_slice)] // See arti#2571
105
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
106
    use super::*;
107
    use Bound::{Excluded as Excl, Included as Incl, Unbounded};
108
    use std::fmt::Debug;
109
    use web_time_compat::{Duration, SystemTime, SystemTimeExt};
110

            
111
    /// A helper that computes the intersection of `range1` and `range2`.
112
    ///
113
    /// This function also asserts that the intersection operation is commutative.
114
    fn intersect<'a, T, R: RangeBounds<T>>(
115
        range1: &'a R,
116
        range2: &'a R,
117
    ) -> Option<(Bound<&'a T>, Bound<&'a T>)>
118
    where
119
        T: PartialEq + Ord + Debug,
120
    {
121
        let intersection1 = range1.intersect(range2);
122
        let intersection2 = range2.intersect(range1);
123

            
124
        assert_eq!(intersection1, intersection2);
125

            
126
        intersection1
127
    }
128

            
129
    /// A helper for randomly generating either an inclusive or an exclusive bound with a
130
    /// particular value.
131
    fn random_bound<T>(value: T) -> Bound<T> {
132
        if rand::random() {
133
            Bound::Included(value)
134
        } else {
135
            Bound::Excluded(value)
136
        }
137
    }
138

            
139
    #[test]
140
    fn no_overlap() {
141
        #[allow(clippy::type_complexity)]
142
        const NON_OVERLAPPING_RANGES: &[(
143
            (Bound<usize>, Bound<usize>),
144
            (Bound<usize>, Bound<usize>),
145
        )] = &[
146
            // (1, 2) and (3, 4)
147
            ((Excl(1), Excl(2)), (Excl(3), Excl(4))),
148
            // (1, 2) and (2, 3)
149
            ((Excl(1), Excl(2)), (Excl(2), Excl(3))),
150
            // (1, 2) and [2, 3)
151
            ((Excl(1), Excl(2)), (Incl(2), Excl(3))),
152
            // (1, 2) and [2, 3]
153
            ((Excl(1), Excl(2)), (Incl(3), Incl(4))),
154
            // (-inf, 2) and [2, 3]
155
            ((Unbounded, Excl(2)), (Incl(2), Incl(3))),
156
            // (-inf, 2) and (2, inf)
157
            ((Unbounded, Excl(2)), (Excl(2), Unbounded)),
158
            // (-inf, 2) and [2, inf)
159
            ((Unbounded, Excl(2)), (Incl(2), Unbounded)),
160
        ];
161

            
162
        for (range1, range2) in NON_OVERLAPPING_RANGES {
163
            let intersection = intersect(range1, range2);
164
            assert!(
165
                intersection.is_none(),
166
                "{:?} and {:?} => {:?}",
167
                range1,
168
                range2,
169
                intersection
170
            );
171
        }
172
    }
173

            
174
    #[test]
175
    fn intersect_unbounded_start() {
176
        // (-inf, 3)
177
        let range1 = (Unbounded, Excl(3));
178
        // [2, 5]
179
        let range2 = (Incl(2), Incl(5));
180

            
181
        let intersection = intersect(&range1, &range2).unwrap();
182

            
183
        // intersection = [2 3]
184
        assert_eq!(intersection.start_bound(), Bound::Included(&2));
185
        assert_eq!(intersection.end_bound(), Bound::Excluded(&3));
186
    }
187

            
188
    #[test]
189
    fn intersect_unbounded_end() {
190
        // (8, inf)
191
        let range1 = (Excl(8), Unbounded);
192
        // [8, 20]
193
        let range2 = (Incl(8), Incl(20));
194

            
195
        let intersection = intersect(&range1, &range2).unwrap();
196

            
197
        // intersection = (8, 20]
198
        assert_eq!(intersection.start_bound(), Bound::Excluded(&8));
199
        assert_eq!(intersection.end_bound(), Bound::Included(&20));
200
    }
201

            
202
    #[test]
203
    fn intersect_unbounded_range() {
204
        #[allow(clippy::type_complexity)]
205
        const RANGES: &[(Bound<usize>, Bound<usize>)] = &[
206
            // (1, 2)
207
            (Excl(1), Excl(2)),
208
            // (1, 2]
209
            (Excl(1), Incl(2)),
210
            // [1, 2]
211
            (Incl(1), Incl(2)),
212
            // [1, 2)
213
            (Incl(1), Excl(2)),
214
            // (1, inf)
215
            (Excl(1), Unbounded),
216
            // [1, inf)
217
            (Incl(1), Unbounded),
218
            // (-inf, 2)
219
            (Unbounded, Excl(2)),
220
            // (-inf, 2]
221
            (Unbounded, Incl(2)),
222
        ];
223

            
224
        // The intersection of any interval I with (Unbounded, Unbounded) will be I.
225
        let range1 = (Unbounded, Unbounded);
226

            
227
        for range2 in RANGES {
228
            let range2 = (range2.0.as_ref(), range2.1.as_ref());
229
            assert_eq!(intersect(&range1, &range2).unwrap(), range2);
230
        }
231
    }
232

            
233
    #[test]
234
    fn intersect_time_bounds() {
235
        const MIN: Duration = Duration::from_secs(60);
236

            
237
        // time (relative to now):  0   1   2   3
238
        //                          |   |   |   |
239
        // [t1, t2]:                [.......]
240
        // [t3, t4]:                    [.......]
241
        // intersection:                [...]
242
        let now = SystemTime::get();
243
        let t1 = now;
244
        let t2 = now + 2 * MIN;
245

            
246
        let t3 = now + 1 * MIN;
247
        let t4 = now + 3 * MIN;
248

            
249
        let b1 = (Bound::Included(t1), Bound::Included(t2));
250
        let b2 = (Bound::Included(t3), Bound::Included(t4));
251
        let expected = (Bound::Included(&t3), Bound::Included(&t2));
252
        assert_eq!(intersect(&b1, &b2).unwrap(), expected);
253

            
254
        //  t1  -  -  t2  -  -
255
        //                   t3  -  -  t4
256
        //
257
        // time (relative to now):  0   1   2   3   4   5   6   7
258
        //                          |   |   |   |   |   |   |   |
259
        // [t1, t2]:                [.......]
260
        // [t3, t4]:                                [............]
261
        let t3 = now + 4 * MIN;
262
        let t4 = now + 7 * MIN;
263
        let b2 = (Bound::Included(t3), Bound::Included(t4));
264
        assert!(intersect(&b1, &b2).is_none());
265
    }
266

            
267
    #[test]
268
    fn combinatorial() {
269
        for i in 0..10 {
270
            for j in 0..10 {
271
                for k in 0..10 {
272
                    for l in 0..10 {
273
                        let range1 = (random_bound(i), random_bound(j));
274
                        let range2 = (random_bound(k), random_bound(l));
275

            
276
                        let intersection = intersect(&range1, &range2);
277

            
278
                        for witness in 0..10 {
279
                            let c1 = range1.contains(&witness);
280
                            let c2 = range2.contains(&witness);
281
                            let both_contain_witness = c1 && c2;
282

            
283
                            if both_contain_witness {
284
                                // If both ranges contain `witness` they definitely intersect.
285
                                assert!(intersection.unwrap().contains(&witness));
286
                            } else if let Some(intersection) = intersection {
287
                                // If one of them doesn't contain `witness`, `witness` is
288
                                // definitely not part of the intersection.
289
                                assert!(!intersection.contains(&witness));
290
                            }
291
                        }
292
                    }
293
                }
294
            }
295
        }
296
    }
297
}