use std::cmp::{self, Ord};
use std::ops::{Bound, RangeBounds};
pub trait RangeBoundsExt<T>: RangeBounds<T> {
fn intersect<'a, U: RangeBounds<T>>(
&'a self,
other: &'a U,
) -> Option<(Bound<&'a T>, Bound<&'a T>)>;
}
impl<T, R> RangeBoundsExt<T> for R
where
R: RangeBounds<T>,
T: Ord,
{
fn intersect<'a, U: RangeBounds<T>>(
&'a self,
other: &'a U,
) -> Option<(Bound<&'a T>, Bound<&'a T>)> {
use Bound::*;
let this_start = self.start_bound();
let other_start = other.start_bound();
let this_end = self.end_bound();
let other_end = other.end_bound();
let start = bounds_max(this_start, other_start);
let end = bounds_min(this_end, other_end);
match (start, end) {
(Excluded(start), Excluded(end)) | (Included(start), Excluded(end)) if start == end => {
None
}
(Included(start), Included(end))
| (Included(start), Excluded(end))
| (Excluded(start), Included(end))
| (Excluded(start), Excluded(end))
if start > end =>
{
None
}
_ => Some((start, end)),
}
}
}
fn bounds_max<'a, T: Ord>(b1: Bound<&'a T>, b2: Bound<&'a T>) -> Bound<&'a T> {
use Bound::*;
match (b1, b2) {
(Included(b1), Included(b2)) => Included(cmp::max(b1, b2)),
(Excluded(b1), Excluded(b2)) => Excluded(cmp::max(b1, b2)),
(Excluded(b1), Included(b2)) if b1 >= b2 => Excluded(b1),
(Excluded(_), Included(b2)) => Included(b2),
(Included(b1), Excluded(b2)) if b2 >= b1 => Excluded(b2),
(Included(b1), Excluded(_)) => Included(b1),
(b, Unbounded) | (Unbounded, b) => b,
}
}
fn bounds_min<'a, T: Ord>(b1: Bound<&'a T>, b2: Bound<&'a T>) -> Bound<&'a T> {
use Bound::*;
match (b1, b2) {
(Included(b1), Included(b2)) => Included(cmp::min(b1, b2)),
(Excluded(b1), Excluded(b2)) => Excluded(cmp::min(b1, b2)),
(Excluded(b1), Included(b2)) if b1 <= b2 => Excluded(b1),
(Excluded(_), Included(b2)) => Included(b2),
(Included(b1), Excluded(b2)) if b2 <= b1 => Excluded(b2),
(Included(b1), Excluded(_)) => Included(b1),
(b, Unbounded) | (Unbounded, b) => b,
}
}
#[cfg(test)]
mod test {
#![allow(clippy::bool_assert_comparison)]
#![allow(clippy::clone_on_copy)]
#![allow(clippy::dbg_macro)]
#![allow(clippy::mixed_attributes_style)]
#![allow(clippy::print_stderr)]
#![allow(clippy::print_stdout)]
#![allow(clippy::single_char_pattern)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::unchecked_duration_subtraction)]
#![allow(clippy::useless_vec)]
#![allow(clippy::needless_pass_by_value)]
use super::*;
use std::fmt::Debug;
use std::time::{Duration, SystemTime};
use Bound::{Excluded as Excl, Included as Incl, Unbounded};
fn intersect<'a, T, R: RangeBounds<T>>(
range1: &'a R,
range2: &'a R,
) -> Option<(Bound<&'a T>, Bound<&'a T>)>
where
T: PartialEq + Ord + Debug,
{
let intersection1 = range1.intersect(range2);
let intersection2 = range2.intersect(range1);
assert_eq!(intersection1, intersection2);
intersection1
}
fn random_bound<T>(value: T) -> Bound<T> {
if rand::random() {
Bound::Included(value)
} else {
Bound::Excluded(value)
}
}
#[test]
fn no_overlap() {
#[allow(clippy::type_complexity)]
const NON_OVERLAPPING_RANGES: &[(
(Bound<usize>, Bound<usize>),
(Bound<usize>, Bound<usize>),
)] = &[
((Excl(1), Excl(2)), (Excl(3), Excl(4))),
((Excl(1), Excl(2)), (Excl(2), Excl(3))),
((Excl(1), Excl(2)), (Incl(2), Excl(3))),
((Excl(1), Excl(2)), (Incl(3), Incl(4))),
((Unbounded, Excl(2)), (Incl(2), Incl(3))),
((Unbounded, Excl(2)), (Excl(2), Unbounded)),
((Unbounded, Excl(2)), (Incl(2), Unbounded)),
];
for (range1, range2) in NON_OVERLAPPING_RANGES {
let intersection = intersect(range1, range2);
assert!(
intersection.is_none(),
"{:?} and {:?} => {:?}",
range1,
range2,
intersection
);
}
}
#[test]
fn intersect_unbounded_start() {
let range1 = (Unbounded, Excl(3));
let range2 = (Incl(2), Incl(5));
let intersection = intersect(&range1, &range2).unwrap();
assert_eq!(intersection.start_bound(), Bound::Included(&2));
assert_eq!(intersection.end_bound(), Bound::Excluded(&3));
}
#[test]
fn intersect_unbounded_end() {
let range1 = (Excl(8), Unbounded);
let range2 = (Incl(8), Incl(20));
let intersection = intersect(&range1, &range2).unwrap();
assert_eq!(intersection.start_bound(), Bound::Excluded(&8));
assert_eq!(intersection.end_bound(), Bound::Included(&20));
}
#[test]
fn intersect_unbounded_range() {
#[allow(clippy::type_complexity)]
const RANGES: &[(Bound<usize>, Bound<usize>)] = &[
(Excl(1), Excl(2)),
(Excl(1), Incl(2)),
(Incl(1), Incl(2)),
(Incl(1), Excl(2)),
(Excl(1), Unbounded),
(Incl(1), Unbounded),
(Unbounded, Excl(2)),
(Unbounded, Incl(2)),
];
let range1 = (Unbounded, Unbounded);
for range2 in RANGES {
let range2 = (range2.0.as_ref(), range2.1.as_ref());
assert_eq!(intersect(&range1, &range2).unwrap(), range2);
}
}
#[test]
fn intersect_time_bounds() {
const MIN: Duration = Duration::from_secs(60);
let now = SystemTime::now();
let t1 = now;
let t2 = now + 2 * MIN;
let t3 = now + 1 * MIN;
let t4 = now + 3 * MIN;
let b1 = (Bound::Included(t1), Bound::Included(t2));
let b2 = (Bound::Included(t3), Bound::Included(t4));
let expected = (Bound::Included(&t3), Bound::Included(&t2));
assert_eq!(intersect(&b1, &b2).unwrap(), expected);
let t3 = now + 4 * MIN;
let t4 = now + 7 * MIN;
let b2 = (Bound::Included(t3), Bound::Included(t4));
assert!(intersect(&b1, &b2).is_none());
}
#[test]
fn combinatorial() {
for i in 0..10 {
for j in 0..10 {
for k in 0..10 {
for l in 0..10 {
let range1 = (random_bound(i), random_bound(j));
let range2 = (random_bound(k), random_bound(l));
let intersection = intersect(&range1, &range2);
for witness in 0..10 {
let c1 = range1.contains(&witness);
let c2 = range2.contains(&witness);
let both_contain_witness = c1 && c2;
if both_contain_witness {
assert!(intersection.unwrap().contains(&witness));
} else if let Some(intersection) = intersection {
assert!(!intersection.contains(&witness));
}
}
}
}
}
}
}
}