1use std::cmp::{self, Ord};
5use std::ops::{Bound, RangeBounds};
6
7pub trait RangeBoundsExt<T: Ord>: RangeBounds<T> {
9 fn intersect<'a, U: RangeBounds<T>>(
16 &'a self,
17 other: &'a U,
18 ) -> Option<(Bound<&'a T>, Bound<&'a T>)> {
19 use Bound::*;
20
21 let this_start = self.start_bound();
22 let other_start = other.start_bound();
23 let this_end = self.end_bound();
24 let other_end = other.end_bound();
25
26 let start = bounds_max(this_start, other_start);
27 let end = bounds_min(this_end, other_end);
28
29 match (start, end) {
30 (Excluded(start), Excluded(end)) | (Included(start), Excluded(end)) if start == end => {
31 None
33 }
34 (Included(start), Included(end))
35 | (Included(start), Excluded(end))
36 | (Excluded(start), Included(end))
37 | (Excluded(start), Excluded(end))
38 if start > end =>
39 {
40 None
42 }
43 _ => Some((start, end)),
44 }
45 }
46}
47
48impl<T: Ord, R: RangeBounds<T>> RangeBoundsExt<T> for R {}
49
50fn bounds_max<'a, T: Ord>(b1: Bound<&'a T>, b2: Bound<&'a T>) -> Bound<&'a T> {
54 use Bound::*;
55
56 match (b1, b2) {
57 (Included(b1), Included(b2)) => Included(cmp::max(b1, b2)),
58 (Excluded(b1), Excluded(b2)) => Excluded(cmp::max(b1, b2)),
59
60 (Excluded(b1), Included(b2)) if b1 >= b2 => Excluded(b1),
61 (Excluded(_), Included(b2)) => Included(b2),
62
63 (Included(b1), Excluded(b2)) if b2 >= b1 => Excluded(b2),
64 (Included(b1), Excluded(_)) => Included(b1),
65
66 (b, Unbounded) | (Unbounded, b) => b,
67 }
68}
69
70fn bounds_min<'a, T: Ord>(b1: Bound<&'a T>, b2: Bound<&'a T>) -> Bound<&'a T> {
74 use Bound::*;
75
76 match (b1, b2) {
77 (Included(b1), Included(b2)) => Included(cmp::min(b1, b2)),
78 (Excluded(b1), Excluded(b2)) => Excluded(cmp::min(b1, b2)),
79
80 (Excluded(b1), Included(b2)) if b1 <= b2 => Excluded(b1),
81 (Excluded(_), Included(b2)) => Included(b2),
82
83 (Included(b1), Excluded(b2)) if b2 <= b1 => Excluded(b2),
84 (Included(b1), Excluded(_)) => Included(b1),
85
86 (b, Unbounded) | (Unbounded, b) => b,
87 }
88}
89
90#[cfg(test)]
91mod test {
92 #![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)] 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 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 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 ((Excl(1), Excl(2)), (Excl(3), Excl(4))),
148 ((Excl(1), Excl(2)), (Excl(2), Excl(3))),
150 ((Excl(1), Excl(2)), (Incl(2), Excl(3))),
152 ((Excl(1), Excl(2)), (Incl(3), Incl(4))),
154 ((Unbounded, Excl(2)), (Incl(2), Incl(3))),
156 ((Unbounded, Excl(2)), (Excl(2), Unbounded)),
158 ((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 let range1 = (Unbounded, Excl(3));
178 let range2 = (Incl(2), Incl(5));
180
181 let intersection = intersect(&range1, &range2).unwrap();
182
183 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 let range1 = (Excl(8), Unbounded);
192 let range2 = (Incl(8), Incl(20));
194
195 let intersection = intersect(&range1, &range2).unwrap();
196
197 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 (Excl(1), Excl(2)),
208 (Excl(1), Incl(2)),
210 (Incl(1), Incl(2)),
212 (Incl(1), Excl(2)),
214 (Excl(1), Unbounded),
216 (Incl(1), Unbounded),
218 (Unbounded, Excl(2)),
220 (Unbounded, Incl(2)),
222 ];
223
224 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 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 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 assert!(intersection.unwrap().contains(&witness));
286 } else if let Some(intersection) = intersection {
287 assert!(!intersection.contains(&witness));
290 }
291 }
292 }
293 }
294 }
295 }
296 }
297}