1use std::cmp::{self, Ord};
5use std::ops::{Bound, RangeBounds};
6
7pub trait RangeBoundsExt<T>: 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}
20
21impl<T, R> RangeBoundsExt<T> for R
22where
23 R: RangeBounds<T>,
24 T: Ord,
25{
26 fn intersect<'a, U: RangeBounds<T>>(
27 &'a self,
28 other: &'a U,
29 ) -> Option<(Bound<&'a T>, Bound<&'a T>)> {
30 use Bound::*;
31
32 let this_start = self.start_bound();
33 let other_start = other.start_bound();
34 let this_end = self.end_bound();
35 let other_end = other.end_bound();
36
37 let start = bounds_max(this_start, other_start);
38 let end = bounds_min(this_end, other_end);
39
40 match (start, end) {
41 (Excluded(start), Excluded(end)) | (Included(start), Excluded(end)) if start == end => {
42 None
44 }
45 (Included(start), Included(end))
46 | (Included(start), Excluded(end))
47 | (Excluded(start), Included(end))
48 | (Excluded(start), Excluded(end))
49 if start > end =>
50 {
51 None
53 }
54 _ => Some((start, end)),
55 }
56 }
57}
58
59fn bounds_max<'a, T: Ord>(b1: Bound<&'a T>, b2: Bound<&'a T>) -> Bound<&'a T> {
63 use Bound::*;
64
65 match (b1, b2) {
66 (Included(b1), Included(b2)) => Included(cmp::max(b1, b2)),
67 (Excluded(b1), Excluded(b2)) => Excluded(cmp::max(b1, b2)),
68
69 (Excluded(b1), Included(b2)) if b1 >= b2 => Excluded(b1),
70 (Excluded(_), Included(b2)) => Included(b2),
71
72 (Included(b1), Excluded(b2)) if b2 >= b1 => Excluded(b2),
73 (Included(b1), Excluded(_)) => Included(b1),
74
75 (b, Unbounded) | (Unbounded, b) => b,
76 }
77}
78
79fn bounds_min<'a, T: Ord>(b1: Bound<&'a T>, b2: Bound<&'a T>) -> Bound<&'a T> {
83 use Bound::*;
84
85 match (b1, b2) {
86 (Included(b1), Included(b2)) => Included(cmp::min(b1, b2)),
87 (Excluded(b1), Excluded(b2)) => Excluded(cmp::min(b1, b2)),
88
89 (Excluded(b1), Included(b2)) if b1 <= b2 => Excluded(b1),
90 (Excluded(_), Included(b2)) => Included(b2),
91
92 (Included(b1), Excluded(b2)) if b2 <= b1 => Excluded(b2),
93 (Included(b1), Excluded(_)) => Included(b1),
94
95 (b, Unbounded) | (Unbounded, b) => b,
96 }
97}
98
99#[cfg(test)]
100mod test {
101 #![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_time_subtraction)]
111 #![allow(clippy::useless_vec)]
112 #![allow(clippy::needless_pass_by_value)]
113 #![allow(clippy::string_slice)] use super::*;
116 use Bound::{Excluded as Excl, Included as Incl, Unbounded};
117 use std::fmt::Debug;
118 use web_time_compat::{Duration, SystemTime, SystemTimeExt};
119
120 fn intersect<'a, T, R: RangeBounds<T>>(
124 range1: &'a R,
125 range2: &'a R,
126 ) -> Option<(Bound<&'a T>, Bound<&'a T>)>
127 where
128 T: PartialEq + Ord + Debug,
129 {
130 let intersection1 = range1.intersect(range2);
131 let intersection2 = range2.intersect(range1);
132
133 assert_eq!(intersection1, intersection2);
134
135 intersection1
136 }
137
138 fn random_bound<T>(value: T) -> Bound<T> {
141 if rand::random() {
142 Bound::Included(value)
143 } else {
144 Bound::Excluded(value)
145 }
146 }
147
148 #[test]
149 fn no_overlap() {
150 #[allow(clippy::type_complexity)]
151 const NON_OVERLAPPING_RANGES: &[(
152 (Bound<usize>, Bound<usize>),
153 (Bound<usize>, Bound<usize>),
154 )] = &[
155 ((Excl(1), Excl(2)), (Excl(3), Excl(4))),
157 ((Excl(1), Excl(2)), (Excl(2), Excl(3))),
159 ((Excl(1), Excl(2)), (Incl(2), Excl(3))),
161 ((Excl(1), Excl(2)), (Incl(3), Incl(4))),
163 ((Unbounded, Excl(2)), (Incl(2), Incl(3))),
165 ((Unbounded, Excl(2)), (Excl(2), Unbounded)),
167 ((Unbounded, Excl(2)), (Incl(2), Unbounded)),
169 ];
170
171 for (range1, range2) in NON_OVERLAPPING_RANGES {
172 let intersection = intersect(range1, range2);
173 assert!(
174 intersection.is_none(),
175 "{:?} and {:?} => {:?}",
176 range1,
177 range2,
178 intersection
179 );
180 }
181 }
182
183 #[test]
184 fn intersect_unbounded_start() {
185 let range1 = (Unbounded, Excl(3));
187 let range2 = (Incl(2), Incl(5));
189
190 let intersection = intersect(&range1, &range2).unwrap();
191
192 assert_eq!(intersection.start_bound(), Bound::Included(&2));
194 assert_eq!(intersection.end_bound(), Bound::Excluded(&3));
195 }
196
197 #[test]
198 fn intersect_unbounded_end() {
199 let range1 = (Excl(8), Unbounded);
201 let range2 = (Incl(8), Incl(20));
203
204 let intersection = intersect(&range1, &range2).unwrap();
205
206 assert_eq!(intersection.start_bound(), Bound::Excluded(&8));
208 assert_eq!(intersection.end_bound(), Bound::Included(&20));
209 }
210
211 #[test]
212 fn intersect_unbounded_range() {
213 #[allow(clippy::type_complexity)]
214 const RANGES: &[(Bound<usize>, Bound<usize>)] = &[
215 (Excl(1), Excl(2)),
217 (Excl(1), Incl(2)),
219 (Incl(1), Incl(2)),
221 (Incl(1), Excl(2)),
223 (Excl(1), Unbounded),
225 (Incl(1), Unbounded),
227 (Unbounded, Excl(2)),
229 (Unbounded, Incl(2)),
231 ];
232
233 let range1 = (Unbounded, Unbounded);
235
236 for range2 in RANGES {
237 let range2 = (range2.0.as_ref(), range2.1.as_ref());
238 assert_eq!(intersect(&range1, &range2).unwrap(), range2);
239 }
240 }
241
242 #[test]
243 fn intersect_time_bounds() {
244 const MIN: Duration = Duration::from_secs(60);
245
246 let now = SystemTime::get();
252 let t1 = now;
253 let t2 = now + 2 * MIN;
254
255 let t3 = now + 1 * MIN;
256 let t4 = now + 3 * MIN;
257
258 let b1 = (Bound::Included(t1), Bound::Included(t2));
259 let b2 = (Bound::Included(t3), Bound::Included(t4));
260 let expected = (Bound::Included(&t3), Bound::Included(&t2));
261 assert_eq!(intersect(&b1, &b2).unwrap(), expected);
262
263 let t3 = now + 4 * MIN;
271 let t4 = now + 7 * MIN;
272 let b2 = (Bound::Included(t3), Bound::Included(t4));
273 assert!(intersect(&b1, &b2).is_none());
274 }
275
276 #[test]
277 fn combinatorial() {
278 for i in 0..10 {
279 for j in 0..10 {
280 for k in 0..10 {
281 for l in 0..10 {
282 let range1 = (random_bound(i), random_bound(j));
283 let range2 = (random_bound(k), random_bound(l));
284
285 let intersection = intersect(&range1, &range2);
286
287 for witness in 0..10 {
288 let c1 = range1.contains(&witness);
289 let c2 = range2.contains(&witness);
290 let both_contain_witness = c1 && c2;
291
292 if both_contain_witness {
293 assert!(intersection.unwrap().contains(&witness));
295 } else if let Some(intersection) = intersection {
296 assert!(!intersection.contains(&witness));
299 }
300 }
301 }
302 }
303 }
304 }
305 }
306}