use crate::{
Bound, Boundary1d, ConstInit, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo,
RangeToInclusive, is,
};
#[doc = crate::_tags!(quant)]
#[doc = crate::_doc_location!("num/quant")]
#[doc(hidden)]
#[macro_export]
#[rustfmt::skip]
macro_rules! _interval {
(
/* expressions */
$l:expr, ..= $u:expr) => { $crate::Interval::closed($l, $u) }; ($l:expr, .. $u:expr) => { $crate::Interval::closed_open($l, $u) };
($l:expr, .. ) => { $crate::Interval::closed_unbounded($l) };
($l:expr, <.. $u:expr) => { $crate::Interval::open($l, $u) }; ($l:expr, <..= $u:expr) => { $crate::Interval::open_closed($l, $u) };
($l:expr, <.. ) => { $crate::Interval::open_unbounded($l) };
( ..= $u:expr) => { $crate::Interval::unbounded_closed($u) }; ( .. $u:expr) => { $crate::Interval::unbounded_open($u) };
(
/* blocks */
$l:block ..= $u:block) => { $crate::Interval::closed($l, $u) }; ($l:block .. $u:block) => { $crate::Interval::closed_open($l, $u) };
($l:block .. ) => { $crate::Interval::closed_unbounded($l) };
($l:block <.. $u:block) => { $crate::Interval::open($l, $u) }; ($l:block <..= $u:block) => { $crate::Interval::open_closed($l, $u) };
($l:block <.. ) => { $crate::Interval::open_unbounded($l) };
( ..= $u:block) => { $crate::Interval::unbounded_closed($u) }; ( .. $u:block) => { $crate::Interval::unbounded_open($u) };
(
/* literals */
$l:literal ..= $u:literal) => { $crate::Interval::closed($l, $u) }; ($l:literal .. $u:literal) => { $crate::Interval::closed_open($l, $u) };
($l:literal .. ) => { $crate::Interval::closed_unbounded($l) };
($l:literal <.. $u:literal) => { $crate::Interval::open($l, $u) }; ($l:literal <..= $u:literal) => { $crate::Interval::open_closed($l, $u) };
($l:literal <.. ) => { $crate::Interval::open_unbounded($l) };
( ..= $u:literal) => { $crate::Interval::unbounded_closed($u) }; ( .. $u:literal) => { $crate::Interval::unbounded_open($u) };
(
/* fully unbounded variants (explicit and inferred type) */
.., $T:ty) => { $crate::Interval::<$T>::unbounded() }; (.. ) => { $crate::Interval::unbounded() };
(
$($t:tt)*) => {
compile_error! { "Invalid interval syntax. Expected forms like: \
l..u, l..=u, l<..u, l<..=u, ..x, ..=x, ..,Type" }
};
}
#[doc(inline)]
pub use _interval as interval;
#[doc = crate::_tags!(quant)]
#[doc = crate::_doc_location!("num/quant")]
#[doc(alias = "Range")]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Interval<T> {
pub lower: Bound<T>,
pub upper: Bound<T>,
}
impl<T> ConstInit for Interval<T> {
const INIT: Self = Self::unbounded();
}
impl<T> Interval<T> {
#[must_use]
pub const fn closed(lower: T, upper: T) -> Self {
Self::new(Bound::Included(lower), Bound::Included(upper))
}
#[must_use]
pub const fn closed_open(lower: T, upper: T) -> Self {
Self::new(Bound::Included(lower), Bound::Excluded(upper))
}
#[must_use]
pub const fn closed_unbounded(lower: T) -> Self {
Self::new(Bound::Included(lower), Bound::Unbounded)
}
#[must_use]
pub const fn open(lower: T, upper: T) -> Self {
Self::new(Bound::Excluded(lower), Bound::Excluded(upper))
}
#[must_use]
pub const fn open_closed(lower: T, upper: T) -> Self {
Self::new(Bound::Excluded(lower), Bound::Included(upper))
}
#[must_use]
pub const fn open_unbounded(lower: T) -> Self {
Self::new(Bound::Excluded(lower), Bound::Unbounded)
}
#[must_use]
pub const fn unbounded() -> Self {
Self::new(Bound::Unbounded, Bound::Unbounded)
}
#[must_use]
pub const fn unbounded_closed(upper: T) -> Self {
Self::new(Bound::Unbounded, Bound::Included(upper))
}
#[must_use]
pub const fn unbounded_open(upper: T) -> Self {
Self::new(Bound::Unbounded, Bound::Excluded(upper))
}
}
impl<T> Interval<T> {
#[must_use]
pub const fn new(lower: Bound<T>, upper: Bound<T>) -> Self {
Self { lower, upper }
}
#[must_use] #[rustfmt::skip]
pub fn point(value: T) -> Self where T: Clone {
Self::closed(value.clone(), value)
}
#[must_use] #[rustfmt::skip]
pub fn empty() -> Self where T: Default {
Self::open(T::default(), T::default())
}
#[must_use] #[rustfmt::skip]
pub const fn empty_const() -> Self where T: ConstInit {
Self::open(T::INIT, T::INIT)
}
#[must_use] #[rustfmt::skip]
pub fn empty_with(value: T) -> Self where T: Clone {
Self::open(value.clone(), value)
}
}
impl<T: Copy> Interval<T> {
#[must_use]
pub const fn to_tuple(self) -> (Bound<T>, Bound<T>) {
(self.lower, self.upper)
}
}
#[rustfmt::skip]
impl<T> Interval<T> {
#[must_use]
pub fn into_tuple(self) -> (Bound<T>, Bound<T>) { (self.lower, self.upper) }
#[must_use]
pub fn to_tuple_ref(&self) -> (Bound<&T>, Bound<&T>) { (self.lower_ref(), self.upper_ref()) }
#[must_use]
pub fn lower_ref(&self) -> Bound<&T> { self.lower.as_ref() }
#[must_use]
pub fn upper_ref(&self) -> Bound<&T> { self.upper.as_ref() }
#[must_use]
pub const fn is_bounded(&self) -> bool { self.is_lower_bounded() && self.is_upper_bounded() }
#[must_use]
pub const fn is_lower_bounded(&self) -> bool { !matches!(self.lower, Bound::Unbounded) }
#[must_use]
pub const fn is_upper_bounded(&self) -> bool { !matches!(self.upper, Bound::Unbounded) }
#[must_use]
pub const fn is_lower_open(&self) -> bool { matches!(self.lower, Bound::Excluded(_)) }
#[must_use]
pub const fn is_lower_closed(&self) -> bool { matches!(self.lower, Bound::Included(_)) }
#[must_use]
pub const fn is_upper_open(&self) -> bool { matches!(self.upper, Bound::Excluded(_)) }
#[must_use]
pub const fn is_upper_closed(&self) -> bool { matches!(self.upper, Bound::Included(_)) }
}
impl<T> Interval<T> {
#[must_use]
pub const fn bound(&self, side: Boundary1d) -> &Bound<T> {
match side {
Boundary1d::Lower => &self.lower,
Boundary1d::Upper => &self.upper,
}
}
#[must_use]
pub fn bound_mut(&mut self, side: Boundary1d) -> &mut Bound<T> {
match side {
Boundary1d::Lower => &mut self.lower,
Boundary1d::Upper => &mut self.upper,
}
}
#[must_use]
pub fn bound_as_ref(&self, side: Boundary1d) -> Bound<&T> {
match side {
Boundary1d::Lower => self.lower_ref(),
Boundary1d::Upper => self.upper_ref(),
}
}
}
impl<T: PartialOrd> Interval<T> {
#[must_use]
pub fn is_empty(&self) -> bool {
match (&self.lower, &self.upper) {
(Bound::Unbounded, _) | (_, Bound::Unbounded) => false,
(Bound::Included(l), Bound::Included(u)) => l > u,
(Bound::Included(l), Bound::Excluded(u)) => l >= u,
(Bound::Excluded(l), Bound::Included(u)) => l >= u,
(Bound::Excluded(l), Bound::Excluded(u)) => l >= u,
}
}
#[must_use]
pub fn is_well_ordered(&self) -> bool {
match (&self.lower, &self.upper) {
(Bound::Unbounded, _) | (_, Bound::Unbounded) => true,
(Bound::Included(l), Bound::Included(u)) => l <= u,
(Bound::Included(l), Bound::Excluded(u)) => l < u,
(Bound::Excluded(l), Bound::Included(u)) => l < u,
(Bound::Excluded(l), Bound::Excluded(u)) => l < u,
}
}
#[must_use]
pub fn contains(&self, value: &T) -> bool {
let lower_check = match &self.lower {
Bound::Included(lower) => *lower <= *value,
Bound::Excluded(lower) => *lower < *value,
Bound::Unbounded => true,
};
let upper_check = match &self.upper {
Bound::Included(upper) => *value <= *upper,
Bound::Excluded(upper) => *value < *upper,
Bound::Unbounded => true,
};
lower_check && upper_check
}
#[must_use]
pub fn size(&self) -> Option<T>
where
T: Clone + core::ops::Sub<Output = T>,
{
match (&self.lower, &self.upper) {
(Bound::Included(l), Bound::Included(u)) => {
is![l <= u, Some(u.clone() - l.clone()), None]
}
(Bound::Included(l), Bound::Excluded(u)) => {
is![l < u, Some(u.clone() - l.clone()), None]
}
(Bound::Excluded(l), Bound::Included(u)) => {
is![l < u, Some(u.clone() - l.clone()), None]
}
(Bound::Excluded(l), Bound::Excluded(u)) => {
is![l < u, Some(u.clone() - l.clone()), None]
}
_ => None, }
}
}
#[rustfmt::skip]
mod impl_traits {
use super::*;
use crate::{IncompatibleBounds, Ordering, RangeBounds};
impl<T> Default for Interval<T> {
fn default() -> Self {
Self::unbounded()
}
}
impl<T> From<RangeInclusive<T>> for Interval<T> {
fn from(r: RangeInclusive<T>) -> Self {
let (start, end) = r.into_inner();
Self::closed(start, end)
}
}
impl<T> From<Range<T>> for Interval<T> {
fn from(r: Range<T>) -> Self { Self::closed_open(r.start, r.end) }
}
impl<T> From<RangeFrom<T>> for Interval<T> {
fn from(r: RangeFrom<T>) -> Self { Self::closed_unbounded(r.start) }
}
impl<T> From<RangeFull> for Interval<T> {
fn from(_: RangeFull) -> Self { Self::unbounded() }
}
impl<T> From<RangeTo<T>> for Interval<T> {
fn from(r: RangeTo<T>) -> Self { Self::unbounded_closed(r.end) }
}
impl<T> From<RangeToInclusive<T>> for Interval<T> {
fn from(r: RangeToInclusive<T>) -> Self { Self::unbounded_open(r.end) }
}
impl<T> TryFrom<Interval<T>> for RangeInclusive<T> {
type Error = IncompatibleBounds;
fn try_from(interval: Interval<T>) -> Result<Self, IncompatibleBounds> {
match (interval.lower, interval.upper) {
(Bound::Included(start), Bound::Included(end)) => Ok(start..=end),
_ => Err(IncompatibleBounds),
}
}
}
impl<T> TryFrom<Interval<T>> for Range<T> {
type Error = IncompatibleBounds;
fn try_from(interval: Interval<T>) -> Result<Self, IncompatibleBounds> {
match (interval.lower, interval.upper) {
(Bound::Included(start), Bound::Excluded(end)) => Ok(start..end),
_ => Err(IncompatibleBounds),
}
}
}
impl<T> TryFrom<Interval<T>> for RangeFrom<T> {
type Error = IncompatibleBounds;
fn try_from(interval: Interval<T>) -> Result<Self, IncompatibleBounds> {
match (interval.lower, interval.upper) {
(Bound::Included(start), Bound::Unbounded) => Ok(start..),
_ => Err(IncompatibleBounds),
}
}
}
impl<T> TryFrom<Interval<T>> for RangeFull {
type Error = IncompatibleBounds;
fn try_from(interval: Interval<T>) -> Result<Self, IncompatibleBounds> {
match (interval.lower, interval.upper) {
(Bound::Unbounded, Bound::Unbounded) => Ok(..),
_ => Err(IncompatibleBounds),
}
}
}
impl<T> TryFrom<Interval<T>> for RangeTo<T> {
type Error = IncompatibleBounds;
fn try_from(interval: Interval<T>) -> Result<Self, IncompatibleBounds> {
match (interval.lower, interval.upper) {
(Bound::Unbounded, Bound::Excluded(end)) => Ok(..end),
_ => Err(IncompatibleBounds),
}
}
}
impl<T> TryFrom<Interval<T>> for RangeToInclusive<T> {
type Error = IncompatibleBounds;
fn try_from(interval: Interval<T>) -> Result<Self, IncompatibleBounds> {
match (interval.lower, interval.upper) {
(Bound::Unbounded, Bound::Included(end)) => Ok(..=end),
_ => Err(IncompatibleBounds),
}
}
}
impl<T> RangeBounds<T> for Interval<T> {
fn start_bound(&self) -> Bound<&T> {
self.lower_ref()
}
fn end_bound(&self) -> Bound<&T> {
self.upper_ref()
}
}
impl<T: PartialOrd> PartialOrd for Interval<T> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match compare_bounds(&self.lower, &other.lower) {
Some(Ordering::Equal) => compare_bounds(&self.upper, &other.upper),
ord => ord,
}
}
}
impl<T: Ord> Ord for Interval<T> {
fn cmp(&self, other: &Self) -> Ordering {
match compare_bounds_ord(&self.lower, &other.lower) {
Ordering::Equal => compare_bounds_ord(&self.upper, &other.upper),
ord => ord,
}
}
}
fn compare_bounds<T: PartialOrd>(l: &Bound<T>, u: &Bound<T>) -> Option<Ordering> {
use Bound::{Excluded, Included, Unbounded};
match (l, u) {
(Unbounded, Unbounded) => Some(Ordering::Equal),
(Unbounded, _) => Some(Ordering::Less),
(_, Unbounded) => Some(Ordering::Greater),
(Included(a_val), Included(b_val)) => a_val.partial_cmp(b_val),
(Excluded(a_val), Excluded(b_val)) => a_val.partial_cmp(b_val),
(Included(a_val), Excluded(b_val)) => {
match a_val.partial_cmp(b_val) {
Some(Ordering::Equal) => Some(Ordering::Less),
ord => ord,
}
}
(Excluded(a_val), Included(b_val)) => {
match a_val.partial_cmp(b_val) {
Some(Ordering::Equal) => Some(Ordering::Greater),
ord => ord,
}
}
}
}
fn compare_bounds_ord<T: Ord>(l: &Bound<T>, u: &Bound<T>) -> Ordering {
use Bound::{Excluded, Included, Unbounded};
match (l, u) {
(Unbounded, Unbounded) => Ordering::Equal,
(Unbounded, _) => Ordering::Less,
(_, Unbounded) => Ordering::Greater,
(Included(a_val), Included(b_val)) => a_val.cmp(b_val),
(Excluded(a_val), Excluded(b_val)) => a_val.cmp(b_val),
(Included(a_val), Excluded(b_val)) => {
match a_val.cmp(b_val) {
Ordering::Equal => Ordering::Less,
ord => ord,
}
}
(Excluded(a_val), Included(b_val)) => {
match a_val.cmp(b_val) {
Ordering::Equal => Ordering::Greater,
ord => ord,
}
}
}
}
}
#[cfg(test)]
#[rustfmt::skip]
mod tests {
use super::{Interval, interval};
#[test]
#[allow(unused_parens, reason = "testing expressions")]
fn interval_macro_expr() {
assert_eq![interval![(5+5), ..= (10+10)], Interval::closed(10, 20)];
assert_eq![interval![(5+5), .. (10+10)], Interval::closed_open(10, 20)];
assert_eq![interval![(5+5), .. ], Interval::closed_unbounded(10)];
assert_eq![interval![(5+5), <.. (10+10)], Interval::open(10, 20)];
assert_eq![interval![(5+5), <..= (10+10)], Interval::open_closed(10, 20)];
assert_eq![interval![(5+5), <.. ], Interval::open_unbounded(10)];
assert_eq![interval![.., i32 ], Interval::<i32>::unbounded()]; assert_eq![interval![..= (10+10)], Interval::unbounded_closed(20)];
assert_eq![interval![.. (10+10)], Interval::unbounded_open(20)];
}
#[test]
#[allow(unused_braces, reason = "testing blocks")]
fn interval_macro_block() {
assert_eq![interval![{2*5} ..= {2*10}], Interval::closed(10, 20)];
assert_eq![interval![{2*5} .. {2*10}], Interval::closed_open(10, 20)];
assert_eq![interval![{2*5} .. ], Interval::closed_unbounded(10)];
assert_eq![interval![{2*5} <.. {2*10}], Interval::open(10, 20)];
assert_eq![interval![{2*5} <..= {2*10}], Interval::open_closed(10, 20)];
assert_eq![interval![{2*5} <.. ], Interval::open_unbounded(10)];
assert_eq![interval![..= {2*10}], Interval::unbounded_closed(20)];
assert_eq![interval![.. {2*10}], Interval::unbounded_open(20)];
}
#[test]
fn interval_macro_literal() {
assert_eq![interval![10 ..= 20], Interval::closed(10, 20)];
assert_eq![interval![10 .. 20], Interval::closed_open(10, 20)];
assert_eq![interval![10 .. ], Interval::closed_unbounded(10)];
assert_eq![interval![10 <.. 20], Interval::open(10, 20)];
assert_eq![interval![10 <..= 20], Interval::open_closed(10, 20)];
assert_eq![interval![10 <.. ], Interval::open_unbounded(10)];
assert_eq![interval![.. ], Interval::<i32>::unbounded()]; assert_eq![interval![..= 20], Interval::unbounded_closed(20)];
assert_eq![interval![.. 20], Interval::unbounded_open(20)];
}
}