use std::ops::Bound;
#[derive(Debug, Copy, Clone)]
pub struct LengthRange {
min: usize,
max: Option<usize>,
}
impl LengthRange {
pub fn new<R>(range: R) -> Self
where
R: std::ops::RangeBounds<usize>,
{
let min = match range.start_bound() {
Bound::Included(&n) => n,
Bound::Excluded(&n) => n.saturating_add(1),
Bound::Unbounded => 0,
};
let max = match range.end_bound() {
Bound::Included(&n) => Some(n),
Bound::Excluded(&n) => Some(n.saturating_sub(1)),
Bound::Unbounded => None,
};
Self { min, max }
}
pub fn within(&self, value: usize) -> bool {
value >= self.min && self.max.is_none_or(|m| value <= m)
}
pub fn min(&self) -> usize {
self.min
}
pub fn max(&self) -> Option<usize> {
self.max
}
}
impl std::fmt::Display for LengthRange {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.max() {
Some(max) => write!(f, "({}; {})", self.min(), max),
None => write!(f, "({}; unbounded)", self.min()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::ops::Bound;
#[test]
fn test_new_inclusive_ranges() {
let r = LengthRange::new(3..=5);
assert_eq!(r.min(), 3);
assert_eq!(r.max(), Some(5));
assert!(r.within(3));
assert!(r.within(5));
assert!(!r.within(2));
assert!(!r.within(6));
}
#[test]
fn test_new_exclusive_ranges() {
let r = LengthRange::new(3..5);
assert_eq!(r.min(), 3);
assert_eq!(r.max(), Some(4));
assert!(r.within(3));
assert!(r.within(4));
assert!(!r.within(5));
}
#[test]
fn test_unbounded_start() {
let r = LengthRange::new(..5);
assert_eq!(r.min(), 0);
assert_eq!(r.max(), Some(4));
assert!(r.within(0));
assert!(r.within(4));
assert!(!r.within(5));
}
#[test]
fn test_unbounded_end() {
let r = LengthRange::new(3..);
assert_eq!(r.min(), 3);
assert_eq!(r.max(), None);
assert!(r.within(3));
assert!(r.within(1000));
assert!(!r.within(2));
}
#[test]
fn test_fully_unbounded() {
let r = LengthRange::new(..);
assert_eq!(r.min(), 0);
assert_eq!(r.max(), None);
assert!(r.within(0));
assert!(r.within(9999));
}
#[test]
fn test_start_excluded_edge() {
let r = LengthRange::new((Bound::Excluded(0), Bound::Included(5)));
assert_eq!(r.min(), 1);
assert_eq!(r.max(), Some(5));
assert!(!r.within(0));
assert!(r.within(1));
}
#[test]
fn test_end_excluded_edge() {
let r = LengthRange::new((Bound::Included(0), Bound::Excluded(1)));
assert_eq!(r.min(), 0);
assert_eq!(r.max(), Some(0));
assert!(r.within(0));
assert!(!r.within(1));
}
#[test]
fn test_display() {
let r = LengthRange::new(1..=3);
assert_eq!(r.to_string(), "(1; 3)");
let r = LengthRange::new(5..);
assert_eq!(r.to_string(), "(5; unbounded)");
}
#[test]
fn test_empty() {
let r = LengthRange::new(10..2);
assert!(!r.within(5));
assert!(!r.within(10));
assert!(!r.within(2));
}
}