intervalmap 0.1.1

An interval set/map library inspired by Boost.Icl
Documentation
use std::ops::{Range, RangeFrom, RangeFull, RangeInclusive, RangeTo};

use crate::IndexType;

/// A half-open interval `[start, end)`.
///
/// This is a helper struct used internally for interval operations.
/// Intervals are represented as half-open ranges matching Rust's `Range<T>`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Interval<Ix> {
    /// The inclusive start of the interval.
    pub start: Ix,
    /// The exclusive end of the interval.
    pub end: Ix,
}

impl<Ix: IndexType> Interval<Ix> {
    /// Creates a new interval `[start, end)`.
    ///
    /// # Panics
    ///
    /// Panics if `start > end`.
    #[inline]
    pub fn new(start: Ix, end: Ix) -> Self {
        debug_assert!(start <= end, "Interval start must be <= end");
        Interval { start, end }
    }

    /// Returns `true` if this interval is empty (start == end).
    #[inline]
    pub fn is_empty(&self) -> bool {
        self.start == self.end
    }

    /// Returns `true` if this interval contains the given point.
    ///
    /// A point `p` is contained if `start <= p < end`.
    #[inline]
    pub fn contains_point(&self, point: Ix) -> bool {
        self.start <= point && point < self.end
    }

    /// Returns `true` if this interval overlaps with another.
    ///
    /// Two intervals overlap if they share at least one point.
    #[inline]
    pub fn overlaps(&self, other: &Interval<Ix>) -> bool {
        self.start < other.end && other.start < self.end
    }

    /// Returns `true` if this interval touches another (adjacent or overlapping).
    ///
    /// Two intervals touch if they are adjacent (one's end equals other's start)
    /// or if they overlap.
    #[inline]
    pub fn touches(&self, other: &Interval<Ix>) -> bool {
        self.start <= other.end && other.start <= self.end
    }

    /// Converts this interval to a `Range<Ix>`.
    #[inline]
    pub fn to_range(&self) -> Range<Ix> {
        self.start..self.end
    }
}

impl<Ix: IndexType> From<Range<Ix>> for Interval<Ix> {
    fn from(range: Range<Ix>) -> Self {
        Interval::new(range.start, range.end)
    }
}

impl<Ix: IndexType> From<Interval<Ix>> for Range<Ix> {
    fn from(interval: Interval<Ix>) -> Self {
        interval.start..interval.end
    }
}

impl<Ix: IndexType> From<RangeInclusive<Ix>> for Interval<Ix> {
    /// Converts an inclusive range `a..=b` to a half-open interval `[a, b+1)`.
    ///
    /// # Panics
    ///
    /// Panics if `b == Ix::MAX` since `b + 1` would overflow.
    fn from(range: RangeInclusive<Ix>) -> Self {
        let start = *range.start();
        let end = range
            .end()
            .checked_add(&Ix::one())
            .expect("RangeInclusive end is at MAX; cannot represent as half-open interval");
        Interval::new(start, end)
    }
}

impl<Ix: IndexType> From<RangeFrom<Ix>> for Interval<Ix> {
    /// Converts an unbounded-end range `a..` to interval `[a, MAX)`.
    fn from(range: RangeFrom<Ix>) -> Self {
        Interval::new(range.start, Ix::max_value())
    }
}

impl<Ix: IndexType> From<RangeTo<Ix>> for Interval<Ix> {
    /// Converts an unbounded-start range `..b` to interval `[MIN, b)`.
    fn from(range: RangeTo<Ix>) -> Self {
        Interval::new(Ix::min_value(), range.end)
    }
}

impl<Ix: IndexType> From<RangeFull> for Interval<Ix> {
    /// Converts a full range `..` to interval `[MIN, MAX)`.
    fn from(_: RangeFull) -> Self {
        Interval::new(Ix::min_value(), Ix::max_value())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_interval_new() {
        let interval = Interval::new(0u32, 10);
        assert_eq!(interval.start, 0);
        assert_eq!(interval.end, 10);
    }

    #[test]
    fn test_interval_is_empty() {
        assert!(Interval::new(5u32, 5).is_empty());
        assert!(!Interval::new(5u32, 10).is_empty());
    }

    #[test]
    fn test_interval_contains_point() {
        let interval = Interval::new(5u32, 10);
        assert!(!interval.contains_point(4));
        assert!(interval.contains_point(5));
        assert!(interval.contains_point(7));
        assert!(interval.contains_point(9));
        assert!(!interval.contains_point(10));
    }

    #[test]
    fn test_interval_overlaps() {
        let a = Interval::new(0u32, 10);
        let b = Interval::new(5, 15);
        let c = Interval::new(10, 20);
        let d = Interval::new(0, 5);

        assert!(a.overlaps(&b)); // partial overlap
        assert!(!a.overlaps(&c)); // adjacent, no overlap
        assert!(a.overlaps(&d)); // contained
    }

    #[test]
    fn test_interval_touches() {
        let a = Interval::new(0u32, 10);
        let b = Interval::new(5, 15);
        let c = Interval::new(10, 20);
        let d = Interval::new(20, 30);

        assert!(a.touches(&b)); // overlapping
        assert!(a.touches(&c)); // adjacent
        assert!(!a.touches(&d)); // gap between
    }

    #[test]
    fn test_interval_from_range() {
        let range = 5u32..10;
        let interval: Interval<u32> = range.into();
        assert_eq!(interval.start, 5);
        assert_eq!(interval.end, 10);
    }

    #[test]
    fn test_interval_from_range_inclusive() {
        let interval: Interval<u32> = (5u32..=10).into();
        assert_eq!(interval.start, 5);
        assert_eq!(interval.end, 11); // inclusive 10 becomes exclusive 11
    }

    #[test]
    fn test_interval_from_range_inclusive_at_zero() {
        let interval: Interval<u8> = (0u8..=0).into();
        assert_eq!(interval.start, 0);
        assert_eq!(interval.end, 1);
    }

    #[test]
    #[should_panic(expected = "RangeInclusive end is at MAX")]
    fn test_interval_from_range_inclusive_overflow() {
        let _interval: Interval<u8> = (0u8..=255).into();
    }

    #[test]
    fn test_interval_from_range_from() {
        let interval: Interval<u32> = (100u32..).into();
        assert_eq!(interval.start, 100);
        assert_eq!(interval.end, u32::MAX);
    }

    #[test]
    fn test_interval_from_range_to() {
        let interval: Interval<u32> = (..100u32).into();
        assert_eq!(interval.start, u32::MIN);
        assert_eq!(interval.end, 100);
    }

    #[test]
    fn test_interval_from_range_to_signed() {
        let interval: Interval<i32> = (..0i32).into();
        assert_eq!(interval.start, i32::MIN);
        assert_eq!(interval.end, 0);
    }

    #[test]
    fn test_interval_from_range_full() {
        let interval: Interval<u32> = (..).into();
        assert_eq!(interval.start, u32::MIN);
        assert_eq!(interval.end, u32::MAX);
    }

    #[test]
    fn test_interval_from_range_full_signed() {
        let interval: Interval<i16> = (..).into();
        assert_eq!(interval.start, i16::MIN);
        assert_eq!(interval.end, i16::MAX);
    }
}