rangebounds.rs
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 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 // The interval (n, n) = [n, n) = {} (empty set). 43 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 // For any a > b, the intervals [a, b], [a, b), (a, b], (a, b) are empty. 52 None 53 } 54 _ => Some((start, end)), 55 } 56 } 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 fn 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 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 fn 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)] 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 }