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_duration_subtraction)]
111 #![allow(clippy::useless_vec)]
112 #![allow(clippy::needless_pass_by_value)]
113 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 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 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 ((Excl(1), Excl(2)), (Excl(3), Excl(4))),
156 ((Excl(1), Excl(2)), (Excl(2), Excl(3))),
158 ((Excl(1), Excl(2)), (Incl(2), Excl(3))),
160 ((Excl(1), Excl(2)), (Incl(3), Incl(4))),
162 ((Unbounded, Excl(2)), (Incl(2), Incl(3))),
164 ((Unbounded, Excl(2)), (Excl(2), Unbounded)),
166 ((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 let range1 = (Unbounded, Excl(3));
186 let range2 = (Incl(2), Incl(5));
188
189 let intersection = intersect(&range1, &range2).unwrap();
190
191 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 let range1 = (Excl(8), Unbounded);
200 let range2 = (Incl(8), Incl(20));
202
203 let intersection = intersect(&range1, &range2).unwrap();
204
205 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 (Excl(1), Excl(2)),
216 (Excl(1), Incl(2)),
218 (Incl(1), Incl(2)),
220 (Incl(1), Excl(2)),
222 (Excl(1), Unbounded),
224 (Incl(1), Unbounded),
226 (Unbounded, Excl(2)),
228 (Unbounded, Incl(2)),
230 ];
231
232 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 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 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 assert!(intersection.unwrap().contains(&witness));
294 } else if let Some(intersection) = intersection {
295 assert!(!intersection.contains(&witness));
298 }
299 }
300 }
301 }
302 }
303 }
304 }
305}