use core::cmp::Ordering;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Range {
pub lo: u64,
pub hi: u64,
}
impl Range {
#[inline]
pub fn new(lo: u64, hi: u64) -> Self {
debug_assert!(hi >= lo, "range {lo}..{hi} inverted");
Range { lo, hi }
}
#[inline]
pub fn len(&self) -> u64 {
self.hi.saturating_sub(self.lo)
}
#[inline]
pub fn is_empty(&self) -> bool {
self.hi <= self.lo
}
}
#[derive(Clone, Default, Debug)]
pub struct IntervalSet {
iv: Vec<Range>,
}
impl IntervalSet {
pub fn new() -> Self {
IntervalSet { iv: Vec::new() }
}
pub fn full(size: u64) -> Self {
if size == 0 {
return Self::new();
}
IntervalSet {
iv: vec![Range::new(0, size)],
}
}
pub fn is_empty(&self) -> bool {
self.iv.is_empty()
}
pub fn len(&self) -> usize {
self.iv.len()
}
pub fn ranges(&self) -> &[Range] {
&self.iv
}
pub fn total(&self) -> u64 {
self.iv.iter().map(|r| r.len()).sum()
}
pub fn take_front(&mut self, n: u64) -> Option<Range> {
if n == 0 {
return None;
}
let first = *self.iv.first()?;
if first.len() <= n {
self.iv.remove(0);
Some(first)
} else {
let out = Range::new(first.lo, first.lo + n);
self.iv[0] = Range::new(first.lo + n, first.hi);
Some(out)
}
}
pub fn insert(&mut self, r: Range) {
if r.is_empty() {
return;
}
let pos = self
.iv
.binary_search_by(|p| {
if p.hi < r.lo {
Ordering::Less
} else if p.lo > r.hi {
Ordering::Greater
} else {
Ordering::Equal
}
})
.unwrap_or_else(|e| e);
let mut lo = r.lo;
let mut hi = r.hi;
let mut end = pos;
while end < self.iv.len() && self.iv[end].lo <= hi {
lo = lo.min(self.iv[end].lo);
hi = hi.max(self.iv[end].hi);
end += 1;
}
let mut start = pos;
while start > 0 && self.iv[start - 1].hi >= lo {
start -= 1;
lo = lo.min(self.iv[start].lo);
hi = hi.max(self.iv[start].hi);
}
self.iv.splice(start..end, [Range::new(lo, hi)]);
}
pub fn remove(&mut self, lo: u64, hi: u64) {
if hi <= lo {
return;
}
let mut out: Vec<Range> = Vec::with_capacity(self.iv.len() + 1);
for r in self.iv.iter().copied() {
if r.hi <= lo || r.lo >= hi {
out.push(r);
continue;
}
if r.lo < lo {
out.push(Range::new(r.lo, lo));
}
if r.hi > hi {
out.push(Range::new(hi, r.hi));
}
}
self.iv = out;
}
pub fn invariant_holds(&self) -> bool {
for w in self.iv.windows(2) {
if w[0].hi >= w[1].lo || w[0].is_empty() {
return false;
}
}
self.iv.last().map(|r| !r.is_empty()).unwrap_or(true)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn take_front_splits_and_preserves_total() {
let mut s = IntervalSet::full(1000);
let a = s.take_front(300).unwrap();
assert_eq!((a.lo, a.hi), (0, 300));
assert_eq!(s.total(), 700);
let b = s.take_front(900).unwrap();
assert_eq!((b.lo, b.hi), (300, 1000));
assert!(s.is_empty());
assert!(s.take_front(10).is_none());
}
#[test]
fn insert_coalesces_adjacent_and_overlapping() {
let mut s = IntervalSet::new();
s.insert(Range::new(10, 20));
s.insert(Range::new(20, 30)); assert_eq!(s.len(), 1);
assert_eq!(s.total(), 20);
s.insert(Range::new(25, 40)); assert_eq!(s.len(), 1);
assert_eq!(s.ranges()[0], Range::new(10, 40));
s.insert(Range::new(100, 110)); assert_eq!(s.len(), 2);
assert!(s.invariant_holds());
}
#[test]
fn insert_bridges_two_ranges() {
let mut s = IntervalSet::new();
s.insert(Range::new(0, 10));
s.insert(Range::new(20, 30));
s.insert(Range::new(10, 20));
assert_eq!(s.len(), 1);
assert_eq!(s.ranges()[0], Range::new(0, 30));
}
#[test]
fn remove_splits() {
let mut s = IntervalSet::full(100);
s.remove(30, 40);
assert_eq!(s.len(), 2);
assert_eq!(s.total(), 90);
assert!(s.invariant_holds());
}
#[test]
fn empty_and_degenerate_inserts_are_noops() {
let mut s = IntervalSet::new();
s.insert(Range::new(5, 5));
assert!(s.is_empty());
assert_eq!(IntervalSet::full(0).total(), 0);
}
}