use core::cmp::Ordering;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
pub enum Interval<A> {
#[default]
Empty,
Closed {
lo: A,
hi: A,
},
}
impl<A> Interval<A> {
#[inline]
#[must_use]
pub const fn empty() -> Self {
Interval::Empty
}
#[inline]
#[must_use]
pub fn singleton(a: A) -> Self
where
A: Clone,
{
Interval::Closed {
lo: a.clone(),
hi: a,
}
}
#[inline]
#[must_use]
pub fn new(x: A, y: A) -> Self
where
A: PartialOrd,
{
match x.partial_cmp(&y) {
Some(Ordering::Less | Ordering::Equal) => Interval::Closed { lo: x, hi: y },
_ => Interval::Empty,
}
}
#[inline]
#[must_use]
pub fn endpts(self) -> Option<(A, A)> {
match self {
Interval::Empty => None,
Interval::Closed { lo, hi } => Some((lo, hi)),
}
}
#[inline]
#[must_use]
pub fn contains(&self, p: &A) -> bool
where
A: PartialOrd,
{
match self {
Interval::Empty => false,
Interval::Closed { lo, hi } => lo <= p && p <= hi,
}
}
#[inline]
#[must_use]
pub fn imap<B, F>(self, f: F) -> Interval<B>
where
F: Fn(A) -> B,
B: PartialOrd,
{
match self {
Interval::Empty => Interval::Empty,
Interval::Closed { lo, hi } => Interval::new(f(lo), f(hi)),
}
}
}
#[cfg(feature = "try_trait")]
impl<A> core::ops::Try for Interval<A> {
type Output = (A, A);
type Residual = Interval<core::convert::Infallible>;
#[inline]
fn from_output((lo, hi): Self::Output) -> Self {
Interval::Closed { lo, hi }
}
#[inline]
fn branch(self) -> core::ops::ControlFlow<Self::Residual, Self::Output> {
match self {
Interval::Closed { lo, hi } => core::ops::ControlFlow::Continue((lo, hi)),
Interval::Empty => core::ops::ControlFlow::Break(Interval::Empty),
}
}
}
#[cfg(feature = "try_trait")]
impl<A> core::ops::FromResidual for Interval<A> {
#[inline]
fn from_residual(residual: Interval<core::convert::Infallible>) -> Self {
match residual {
Interval::Empty => Interval::Empty,
Interval::Closed { lo, .. } => match lo {},
}
}
}
#[cfg(feature = "try_trait")]
impl<A> core::ops::Residual<(A, A)> for Interval<core::convert::Infallible> {
type TryType = Interval<A>;
}
impl<A: PartialOrd> PartialOrd for Interval<A> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match (self, other) {
(Interval::Empty, Interval::Empty) => Some(Ordering::Equal),
(Interval::Empty, Interval::Closed { .. }) => Some(Ordering::Less),
(Interval::Closed { .. }, Interval::Empty) => Some(Ordering::Greater),
(Interval::Closed { lo: l1, hi: h1 }, Interval::Closed { lo: l2, hi: h2 }) => {
let l_cmp = l2.partial_cmp(l1)?;
let h_cmp = h1.partial_cmp(h2)?;
let i1_in_i2 = matches!(l_cmp, Ordering::Less | Ordering::Equal)
&& matches!(h_cmp, Ordering::Less | Ordering::Equal);
let i2_in_i1 = matches!(l_cmp, Ordering::Greater | Ordering::Equal)
&& matches!(h_cmp, Ordering::Greater | Ordering::Equal);
match (i1_in_i2, i2_in_i1) {
(true, true) => Some(Ordering::Equal),
(true, false) => Some(Ordering::Less),
(false, true) => Some(Ordering::Greater),
(false, false) => None,
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_is_default() {
let i: Interval<i32> = Interval::default();
assert_eq!(i, Interval::Empty);
}
#[test]
fn empty_below_bounded_in_containment() {
let e: Interval<i32> = Interval::Empty;
let b = Interval::new(1, 3);
assert_eq!(e.partial_cmp(&b), Some(Ordering::Less));
assert_eq!(b.partial_cmp(&e), Some(Ordering::Greater));
}
#[test]
fn containment_strict() {
let small = Interval::new(2, 4);
let big = Interval::new(1, 5);
assert_eq!(small.partial_cmp(&big), Some(Ordering::Less));
assert_eq!(big.partial_cmp(&small), Some(Ordering::Greater));
}
#[test]
fn antichain_in_containment_order() {
let a = Interval::new(1, 4);
let b = Interval::new(2, 5);
assert_eq!(a.partial_cmp(&b), None);
}
#[test]
fn contains_excludes_outside() {
let i = Interval::new(1, 3);
assert!(!i.contains(&0));
assert!(i.contains(&1));
assert!(i.contains(&3));
assert!(!i.contains(&4));
}
}