1use std::cmp::Ordering;
30use std::fmt;
31use std::ops::Add;
32
33pub trait IntervalBound: Copy + PartialOrd + fmt::Debug {
44 type Length: Copy + PartialOrd + Default + Add<Output = Self::Length> + fmt::Debug;
46
47 fn is_admissible(self) -> bool;
49
50 fn distance(start: Self, end: Self) -> Self::Length;
52}
53
54macro_rules! float_bound {
55 ($($t:ty),*) => {$(
56 impl IntervalBound for $t {
57 type Length = $t;
58 fn is_admissible(self) -> bool {
59 self.is_finite()
60 }
61 fn distance(start: Self, end: Self) -> Self::Length {
62 end - start
63 }
64 }
65 )*};
66}
67
68macro_rules! int_bound {
69 ($($t:ty => $len:ty),*) => {$(
70 impl IntervalBound for $t {
71 type Length = $len;
72 fn is_admissible(self) -> bool {
73 true
74 }
75 fn distance(start: Self, end: Self) -> Self::Length {
76 end.abs_diff(start)
77 }
78 }
79 )*};
80}
81
82float_bound!(f32, f64);
83int_bound!(
84 i8 => u8, i16 => u16, i32 => u32, i64 => u64, i128 => u128, isize => usize,
85 u8 => u8, u16 => u16, u32 => u32, u64 => u64, u128 => u128, usize => usize
86);
87
88#[derive(Debug, Clone, Copy, PartialEq, Eq)]
94pub enum IntervalError {
95 Reversed {
99 index: usize,
101 },
102 NotFinite {
104 index: usize,
106 },
107}
108
109impl fmt::Display for IntervalError {
110 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111 match self {
112 IntervalError::Reversed { index } => {
113 write!(f, "interval {index} has start > end")
114 }
115 IntervalError::NotFinite { index } => {
116 write!(f, "interval {index} has a NaN or infinite bound")
117 }
118 }
119 }
120}
121
122impl std::error::Error for IntervalError {}
123
124fn check<T: IntervalBound>(index: usize, start: T, end: T) -> Result<(), IntervalError> {
125 if !start.is_admissible() || !end.is_admissible() {
126 return Err(IntervalError::NotFinite { index });
127 }
128 if start > end {
129 return Err(IntervalError::Reversed { index });
130 }
131 Ok(())
132}
133
134fn cmp<T: IntervalBound>(a: T, b: T) -> Ordering {
136 a.partial_cmp(&b)
137 .expect("IntervalSet bounds are admissible, so comparable")
138}
139
140fn max<T: IntervalBound>(a: T, b: T) -> T {
141 if b > a {
142 b
143 } else {
144 a
145 }
146}
147
148fn min<T: IntervalBound>(a: T, b: T) -> T {
149 if b < a {
150 b
151 } else {
152 a
153 }
154}
155
156#[derive(Debug, Clone, PartialEq)]
177pub struct IntervalSet<T: IntervalBound> {
178 pieces: Vec<(T, T)>,
179}
180
181impl<T: IntervalBound> Default for IntervalSet<T> {
182 fn default() -> Self {
183 Self::new()
184 }
185}
186
187impl<T: IntervalBound> IntervalSet<T> {
188 pub fn new() -> Self {
190 Self { pieces: Vec::new() }
191 }
192
193 pub fn from_intervals<I>(intervals: I) -> Result<Self, IntervalError>
202 where
203 I: IntoIterator<Item = (T, T)>,
204 {
205 let mut pieces = Vec::new();
206 for (index, (start, end)) in intervals.into_iter().enumerate() {
207 check(index, start, end)?;
208 if start < end {
209 pieces.push((start, end));
210 }
211 }
212 pieces.sort_by(|a, b| cmp(a.0, b.0));
213 Ok(Self {
214 pieces: merge_sorted(pieces),
215 })
216 }
217
218 pub fn insert(&mut self, start: T, end: T) -> Result<(), IntervalError> {
223 check(0, start, end)?;
224 if start < end {
225 *self = self.union(&Self {
226 pieces: vec![(start, end)],
227 });
228 }
229 Ok(())
230 }
231
232 pub fn iter(&self) -> impl Iterator<Item = (T, T)> + '_ {
234 self.pieces.iter().copied()
235 }
236
237 pub fn as_slice(&self) -> &[(T, T)] {
239 &self.pieces
240 }
241
242 pub fn len(&self) -> usize {
244 self.pieces.len()
245 }
246
247 pub fn is_empty(&self) -> bool {
249 self.pieces.is_empty()
250 }
251
252 pub fn measure(&self) -> T::Length {
254 self.pieces
255 .iter()
256 .fold(T::Length::default(), |acc, &(s, e)| acc + T::distance(s, e))
257 }
258
259 pub fn contains(&self, t: T) -> bool {
261 if !t.is_admissible() {
262 return false;
263 }
264 let i = self.pieces.partition_point(|&(_, e)| e <= t);
266 self.pieces.get(i).is_some_and(|&(s, _)| s <= t)
267 }
268
269 pub fn union(&self, other: &Self) -> Self {
271 let mut all = Vec::with_capacity(self.pieces.len() + other.pieces.len());
272 let (mut i, mut j) = (0, 0);
273 while i < self.pieces.len() || j < other.pieces.len() {
274 let take_self = match (self.pieces.get(i), other.pieces.get(j)) {
275 (Some(a), Some(b)) => a.0 <= b.0,
276 (Some(_), None) => true,
277 _ => false,
278 };
279 if take_self {
280 all.push(self.pieces[i]);
281 i += 1;
282 } else {
283 all.push(other.pieces[j]);
284 j += 1;
285 }
286 }
287 Self {
288 pieces: merge_sorted(all),
289 }
290 }
291
292 pub fn intersection(&self, other: &Self) -> Self {
294 let mut out = Vec::new();
295 let (mut i, mut j) = (0, 0);
296 while let (Some(&(a0, a1)), Some(&(b0, b1))) = (self.pieces.get(i), other.pieces.get(j)) {
297 let s = max(a0, b0);
298 let e = min(a1, b1);
299 if s < e {
300 out.push((s, e));
301 }
302 if a1 < b1 {
303 i += 1;
304 } else {
305 j += 1;
306 }
307 }
308 Self { pieces: out }
310 }
311
312 pub fn difference(&self, other: &Self) -> Self {
314 let mut out = Vec::new();
315 let mut j = 0;
316 for &(a0, a1) in &self.pieces {
317 let mut cur = a0;
318 while other.pieces.get(j).is_some_and(|&(_, b1)| b1 <= cur) {
320 j += 1;
321 }
322 let mut k = j;
323 while let Some(&(b0, b1)) = other.pieces.get(k) {
324 if b0 >= a1 {
325 break;
326 }
327 if b0 > cur {
328 out.push((cur, b0));
329 }
330 cur = max(cur, b1);
331 if cur >= a1 {
332 break;
333 }
334 k += 1;
335 }
336 if cur < a1 {
337 out.push((cur, a1));
338 }
339 j = k;
342 }
343 Self { pieces: out }
344 }
345
346 pub fn clip(&self, start: T, end: T) -> Result<Self, IntervalError> {
351 check(0, start, end)?;
352 if start >= end {
353 return Ok(Self::new());
354 }
355 Ok(self.intersection(&Self {
356 pieces: vec![(start, end)],
357 }))
358 }
359}
360
361fn merge_sorted<T: IntervalBound>(sorted: Vec<(T, T)>) -> Vec<(T, T)> {
363 let mut out: Vec<(T, T)> = Vec::with_capacity(sorted.len());
364 for (s, e) in sorted {
365 match out.last_mut() {
366 Some(last) if s <= last.1 => last.1 = max(last.1, e),
367 _ => out.push((s, e)),
368 }
369 }
370 out
371}
372
373#[cfg(test)]
374mod tests {
375 use super::*;
376 use proptest::prelude::*;
377
378 fn set(v: &[(i64, i64)]) -> IntervalSet<i64> {
379 IntervalSet::from_intervals(v.iter().copied()).expect("valid test intervals")
380 }
381
382 #[test]
383 fn normalises_overlap_touch_and_empty() {
384 let s = set(&[(5, 7), (1, 3), (3, 4), (2, 2), (6, 9), (11, 12)]);
385 assert_eq!(s.as_slice(), &[(1, 4), (5, 9), (11, 12)]);
386 assert_eq!(s.measure(), 8);
387 assert_eq!(s.len(), 3);
388 }
389
390 #[test]
391 fn refuses_reversed_and_non_finite_with_position() {
392 assert_eq!(
393 IntervalSet::from_intervals([(0, 1), (3, 2)]),
394 Err(IntervalError::Reversed { index: 1 })
395 );
396 assert_eq!(
397 IntervalSet::from_intervals([(0.0, 1.0), (f64::NAN, 2.0)]),
398 Err(IntervalError::NotFinite { index: 1 })
399 );
400 assert_eq!(
401 IntervalSet::from_intervals([(0.0, f64::INFINITY)]),
402 Err(IntervalError::NotFinite { index: 0 })
403 );
404 let mut s = IntervalSet::new();
405 assert_eq!(s.insert(2, 1), Err(IntervalError::Reversed { index: 0 }));
406 assert_eq!(
407 set(&[(0, 5)]).clip(4, 3),
408 Err(IntervalError::Reversed { index: 0 })
409 );
410 }
411
412 #[test]
413 fn set_operations() {
414 let a = set(&[(0, 10), (20, 30)]);
415 let b = set(&[(5, 25)]);
416 assert_eq!(a.union(&b).as_slice(), &[(0, 30)]);
417 assert_eq!(a.intersection(&b).as_slice(), &[(5, 10), (20, 25)]);
418 assert_eq!(a.difference(&b).as_slice(), &[(0, 5), (25, 30)]);
419 assert_eq!(b.difference(&a).as_slice(), &[(10, 20)]);
420 assert_eq!(a.clip(8, 22).unwrap().as_slice(), &[(8, 10), (20, 22)]);
421 assert!(a.clip(3, 3).unwrap().is_empty());
422 }
423
424 #[test]
425 fn difference_where_one_subtrahend_covers_several_pieces() {
426 let a = set(&[(0, 2), (3, 5), (6, 8), (9, 12)]);
427 let b = set(&[(1, 7), (10, 11)]);
428 assert_eq!(
429 a.difference(&b).as_slice(),
430 &[(0, 1), (7, 8), (9, 10), (11, 12)]
431 );
432 }
433
434 #[test]
435 fn contains_is_half_open() {
436 let s = set(&[(1, 3), (5, 6)]);
437 assert!(!s.contains(0));
438 assert!(s.contains(1));
439 assert!(s.contains(2));
440 assert!(!s.contains(3));
441 assert!(s.contains(5));
442 assert!(!s.contains(6));
443 assert!(!IntervalSet::<f64>::new().contains(0.0));
444 assert!(!IntervalSet::from_intervals([(0.0, 1.0)])
445 .unwrap()
446 .contains(f64::NAN));
447 }
448
449 #[test]
450 fn integer_measure_spans_the_whole_type() {
451 let s = set(&[(i64::MIN, 0), (1, i64::MAX)]);
452 assert_eq!(s.measure(), u64::MAX - 1);
453 }
454
455 #[test]
458 fn overlapping_categories_count_once() {
459 let planned = IntervalSet::from_intervals([(0.0, 24.0)]).unwrap();
460 let unplanned = IntervalSet::from_intervals([(10.0, 30.0)]).unwrap();
461 let week = IntervalSet::from_intervals([(0.0, 168.0)]).unwrap();
462 let up = week.difference(&planned.union(&unplanned));
463 assert_eq!(up.measure(), 138.0);
464 }
465
466 fn arb_pieces() -> impl Strategy<Value = Vec<(i64, i64)>> {
467 prop::collection::vec((-50i64..50, 0i64..20), 0..12)
468 .prop_map(|v| v.into_iter().map(|(s, l)| (s, s + l)).collect())
469 }
470
471 fn points(s: &IntervalSet<i64>) -> Vec<bool> {
473 (-60..80).map(|t| s.contains(t)).collect()
474 }
475
476 fn is_normal(s: &IntervalSet<i64>) -> bool {
477 s.as_slice().iter().all(|&(a, b)| a < b) && s.as_slice().windows(2).all(|w| w[0].1 < w[1].0)
478 }
479
480 proptest! {
481 #[test]
482 fn operations_agree_with_pointwise_model(a in arb_pieces(), b in arb_pieces()) {
483 let (sa, sb) = (set(&a), set(&b));
484 let (pa, pb) = (points(&sa), points(&sb));
485 for (op, expect) in [
486 (sa.union(&sb), pa.iter().zip(&pb).map(|(x, y)| *x || *y).collect::<Vec<_>>()),
487 (sa.intersection(&sb), pa.iter().zip(&pb).map(|(x, y)| *x && *y).collect()),
488 (sa.difference(&sb), pa.iter().zip(&pb).map(|(x, y)| *x && !*y).collect()),
489 ] {
490 prop_assert!(is_normal(&op));
491 prop_assert_eq!(points(&op), expect.clone());
492 prop_assert_eq!(op.measure() as usize, expect.iter().filter(|x| **x).count());
494 }
495 }
496
497 #[test]
498 fn measure_is_inclusion_exclusion(a in arb_pieces(), b in arb_pieces()) {
499 let (sa, sb) = (set(&a), set(&b));
500 prop_assert_eq!(
501 sa.union(&sb).measure() + sa.intersection(&sb).measure(),
502 sa.measure() + sb.measure()
503 );
504 prop_assert_eq!(
505 sa.difference(&sb).measure() + sa.intersection(&sb).measure(),
506 sa.measure()
507 );
508 }
509
510 #[test]
511 fn insert_equals_union(a in arb_pieces(), s in -50i64..50, l in 0i64..20) {
512 let mut x = set(&a);
513 x.insert(s, s + l).unwrap();
514 prop_assert_eq!(x, set(&a).union(&set(&[(s, s + l)])));
515 }
516 }
517}