use oxibrain_ports::Timestamp;
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct Interval {
pub start: Timestamp,
pub end: Timestamp,
}
impl Interval {
pub fn new(start: Timestamp, end: Timestamp) -> Self {
debug_assert!(start <= end, "interval start must be <= end");
Self { start, end }
}
pub fn contains(&self, t: Timestamp) -> bool {
self.start <= t && t <= self.end
}
}
pub fn overlaps(a: &Interval, b: &Interval) -> bool {
a.start <= b.end && b.start <= a.end
}
pub fn merge_overlapping(intervals: &mut Vec<Interval>) {
if intervals.len() <= 1 {
return;
}
intervals.sort_by_key(|iv| iv.start);
let mut merged: Vec<Interval> = Vec::with_capacity(intervals.len());
merged.push(intervals[0]);
for &iv in &intervals[1..] {
let last = merged.last_mut().expect("non-empty");
if iv.start <= last.end {
if iv.end > last.end {
last.end = iv.end;
}
} else {
merged.push(iv);
}
}
*intervals = merged;
}
pub fn clip(affirming: &[Interval], denial: &Interval) -> Vec<Interval> {
let mut result: Vec<Interval> = Vec::new();
for aff in affirming {
if !overlaps(aff, denial) {
result.push(*aff);
continue;
}
if aff.start < denial.start {
result.push(Interval::new(
aff.start,
Timestamp(denial.start.millis() - 1),
));
}
if denial.end < aff.end {
result.push(Interval::new(Timestamp(denial.end.millis() + 1), aff.end));
}
}
merge_overlapping(&mut result);
result
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
fn iv(s: i64, e: i64) -> Interval {
Interval::new(Timestamp(s), Timestamp(e))
}
#[test]
fn merge_disjoint_unchanged() {
let mut v = vec![iv(1, 5), iv(10, 15)];
merge_overlapping(&mut v);
assert_eq!(v, vec![iv(1, 5), iv(10, 15)]);
}
#[test]
fn merge_overlapping_test() {
let mut v = vec![iv(1, 5), iv(3, 10)];
merge_overlapping(&mut v);
assert_eq!(v, vec![iv(1, 10)]);
}
#[test]
fn merge_adjacent() {
let mut v = vec![iv(1, 5), iv(6, 10)];
merge_overlapping(&mut v);
assert_eq!(v.len(), 2); }
#[test]
fn merge_touching() {
let mut v = vec![iv(1, 5), iv(5, 10)];
merge_overlapping(&mut v);
assert_eq!(v, vec![iv(1, 10)]);
}
#[test]
fn clip_no_overlap() {
let aff = vec![iv(1, 10)];
let result = clip(&aff, &iv(20, 30));
assert_eq!(result, vec![iv(1, 10)]);
}
#[test]
fn clip_full_cover() {
let aff = vec![iv(5, 10)];
let result = clip(&aff, &iv(1, 20));
assert!(result.is_empty());
}
#[test]
fn clip_partial_left() {
let aff = vec![iv(1, 10)];
let result = clip(&aff, &iv(1, 5));
assert_eq!(result, vec![iv(6, 10)]);
}
#[test]
fn clip_partial_right() {
let aff = vec![iv(1, 10)];
let result = clip(&aff, &iv(7, 15));
assert_eq!(result, vec![iv(1, 6)]);
}
#[test]
fn clip_middle() {
let aff = vec![iv(1, 20)];
let result = clip(&aff, &iv(8, 12));
assert_eq!(result, vec![iv(1, 7), iv(13, 20)]);
}
#[test]
fn overlaps_symmetric() {
let a = iv(1, 5);
let b = iv(3, 10);
assert!(overlaps(&a, &b));
assert!(overlaps(&b, &a));
}
proptest! {
#[test]
fn merge_output_is_disjoint(starts in 1i64..100, lens in 1i64..50, count in 2usize..10) {
let mut v: Vec<Interval> = (0..count)
.map(|i| iv(starts + i as i64 * lens, starts + i as i64 * lens + lens))
.collect();
merge_overlapping(&mut v);
for w in v.windows(2) {
prop_assert!(w[0].end < w[1].start, "intervals must be disjoint after merge");
}
}
#[test]
fn clip_is_subset(aff_start in 1i64..50, aff_len in 1i64..50, d_start in 1i64..100, d_len in 1i64..50) {
let aff = vec![iv(aff_start, aff_start + aff_len)];
let denial = iv(d_start, d_start + d_len);
let clipped = clip(&aff, &denial);
for c in &clipped {
prop_assert!(c.start >= aff[0].start);
prop_assert!(c.end <= aff[0].end);
prop_assert!(!overlaps(c, &denial) || c.start == c.end,
"clipped interval must not overlap denial");
}
}
}
}