From 5fe51129f23ee454142a8b7a2a1d1d2e560f562b Mon Sep 17 00:00:00 2001 From: Eduardo Trujillo <ed@chromabits.com> Date: Sun, 16 Mar 2025 00:01:42 +0000 Subject: [PATCH] feat(rangeset): Introduce a range set data structure --- src/lib.rs | 2 + src/rangeset.rs | 110 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 src/rangeset.rs diff --git a/src/lib.rs b/src/lib.rs index 6cd57b8..9b5b4f1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,8 @@ pub mod cli; pub mod config; /// Range dictionary data structure pub mod rangedict; +/// Range set data structure +pub mod rangeset; /// Ephemeral working directories. pub mod rundir; /// String utilities. diff --git a/src/rangeset.rs b/src/rangeset.rs new file mode 100644 index 0000000..f6e8ee3 --- /dev/null +++ b/src/rangeset.rs @@ -0,0 +1,110 @@ +use std::collections::BTreeMap; + +use thiserror::Error; + +/// Error type for [`RangeSet`] +#[derive(Error, Debug, PartialEq)] +pub enum RangeSetError { + /// Used when an invalid range is provided (e.g. End is before start). + #[error("Invalid range provided")] + InvalidRange, + /// Used when an insertion would result in an overlap. + #[error("Overlapping ranges")] + RangeOverlap, +} + +/// A set of non-overlapping ranges. +#[derive(Debug)] +pub struct RangeSet<RK: Ord + Copy> { + ranges: BTreeMap<RK, RK>, // Maps start -> end +} + +impl<RK: Ord + Copy> RangeSet<RK> { + /// Creates a new empty `RangeSet`. + pub fn new() -> Self { + Self { + ranges: BTreeMap::new(), + } + } + + /// Inserts a new range `[start, end)`, ensuring no overlaps. + pub fn insert(&mut self, start: RK, end: RK) -> Result<(), RangeSetError> { + if start >= end { + return Err(RangeSetError::InvalidRange); + } + + // Find adjacent or overlapping ranges + if let Some((&_prev_start, &prev_end)) = self.ranges.range(..=start).next_back() { + if prev_end >= start { + return Err(RangeSetError::RangeOverlap); + } + } + if let Some((&next_start, _)) = self.ranges.range(start..).next() { + if next_start < end { + return Err(RangeSetError::RangeOverlap); + } + } + + self.ranges.insert(start, end); + Ok(()) + } + + /// Checks if a value exists in any range. + pub fn contains(&self, value: RK) -> bool { + if let Some((&_start, &end)) = self.ranges.range(..=value).next_back() { + return value < end; + } + false + } + + /// Removes a range if it exists. + pub fn remove(&mut self, start: RK, end: RK) -> bool { + if let Some(range_end) = self.ranges.get(&start) { + if *range_end == end { + self.ranges.remove(&start); + return true; + } + } + + false + } + + /// Returns all stored ranges. + pub fn iter(&self) -> impl Iterator<Item = (RK, RK)> + '_ { + self.ranges.iter().map(|(&s, &e)| (s, e)) + } +} + +impl<RK: Copy + Ord> Default for RangeSet<RK> { + fn default() -> Self { + RangeSet::new() + } +} + +#[cfg(test)] +mod tests { + use crate::rangeset::{RangeSet, RangeSetError}; + + #[test] + fn test_insertions() { + let mut rs = RangeSet::new(); + + assert_eq!(rs.insert(10, 20), Ok(())); + assert_eq!(rs.insert(30, 40), Ok(())); + assert_eq!(rs.insert(15, 25), Err(RangeSetError::RangeOverlap)); + assert_eq!(rs.contains(15), true); + assert_eq!(rs.contains(25), false); + } + + #[test] + fn test_removal() { + let mut rs = RangeSet::new(); + + assert_eq!(rs.insert(10, 20), Ok(())); + assert_eq!(rs.insert(30, 40), Ok(())); + assert_eq!(rs.remove(10, 11), false); + assert_eq!(rs.remove(10, 20), true); + assert_eq!(rs.remove(30, 40), true); + assert_eq!(rs.remove(30, 40), false); + } +} -- GitLab