use core::cmp::Ordering;
use num_traits::{One, PrimInt, Unsigned};
pub trait Bound<T: Sized>: From<Option<T>> + Copy {
fn unbound(self) -> Option<T>;
}
pub trait Numerated: Copy + Sized + Ord + Eq {
type Distance: PrimInt + Unsigned;
type Bound: Bound<Self>;
fn add_if_enclosed_by(self, num: Self::Distance, other: Self) -> Option<Self>;
fn sub_if_enclosed_by(self, num: Self::Distance, other: Self) -> Option<Self>;
fn distance(self, other: Self) -> Self::Distance;
fn inc_if_lt(self, other: Self) -> Option<Self> {
self.add_if_enclosed_by(Self::Distance::one(), other)
}
fn dec_if_gt(self, other: Self) -> Option<Self> {
self.sub_if_enclosed_by(Self::Distance::one(), other)
}
fn enclosed_by(self, a: &Self, b: &Self) -> bool {
self <= *a.max(b) && self >= *a.min(b)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, derive_more::From)]
pub struct OptionBound<T>(Option<T>);
impl<T> From<T> for OptionBound<T> {
fn from(value: T) -> Self {
Some(value).into()
}
}
impl<T: Copy> Bound<T> for OptionBound<T> {
fn unbound(self) -> Option<T> {
self.0
}
}
impl<T> PartialOrd for OptionBound<T>
where
T: PartialOrd,
{
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match (self.0.as_ref(), other.0.as_ref()) {
(None, None) => Some(Ordering::Equal),
(None, Some(_)) => Some(Ordering::Greater),
(Some(_), None) => Some(Ordering::Less),
(Some(a), Some(b)) => a.partial_cmp(b),
}
}
}
impl<T> Ord for OptionBound<T>
where
T: Ord,
{
fn cmp(&self, other: &Self) -> Ordering {
match (self.0.as_ref(), other.0.as_ref()) {
(None, None) => Ordering::Equal,
(None, Some(_)) => Ordering::Greater,
(Some(_), None) => Ordering::Less,
(Some(a), Some(b)) => a.cmp(b),
}
}
}
impl<T> PartialEq<T> for OptionBound<T>
where
T: PartialEq,
{
fn eq(&self, other: &T) -> bool {
self.0.as_ref().map(|a| a.eq(other)).unwrap_or(false)
}
}
impl<T> PartialEq<Option<T>> for OptionBound<T>
where
T: PartialEq,
{
fn eq(&self, other: &Option<T>) -> bool {
self.0 == *other
}
}
impl<T> PartialOrd<T> for OptionBound<T>
where
T: PartialOrd,
{
fn partial_cmp(&self, other: &T) -> Option<Ordering> {
self.0
.as_ref()
.map(|a| a.partial_cmp(other))
.unwrap_or(Some(Ordering::Greater))
}
}
macro_rules! impl_for_unsigned {
($($t:ty)*) => ($(
impl Numerated for $t {
type Distance = $t;
type Bound = OptionBound<$t>;
fn add_if_enclosed_by(self, num: Self::Distance, other: Self) -> Option<Self> {
self.checked_add(num).and_then(|res| res.enclosed_by(&self, &other).then_some(res))
}
fn sub_if_enclosed_by(self, num: Self::Distance, other: Self) -> Option<Self> {
self.checked_sub(num).and_then(|res| res.enclosed_by(&self, &other).then_some(res))
}
fn distance(self, other: Self) -> $t {
self.abs_diff(other)
}
}
)*)
}
impl_for_unsigned!(u8 u16 u32 u64 u128 usize);
macro_rules! impl_for_signed {
($($s:ty => $u:ty),*) => {
$(
impl Numerated for $s {
type Distance = $u;
type Bound = OptionBound<$s>;
fn add_if_enclosed_by(self, num: Self::Distance, other: Self) -> Option<Self> {
let res = (self as $u).wrapping_add(num) as $s;
res.enclosed_by(&self, &other).then_some(res)
}
fn sub_if_enclosed_by(self, num: Self::Distance, other: Self) -> Option<Self> {
let res = (self as $u).wrapping_sub(num) as $s;
res.enclosed_by(&self, &other).then_some(res)
}
fn distance(self, other: Self) -> $u {
self.abs_diff(other)
}
}
)*
};
}
impl_for_signed!(i8 => u8, i16 => u16, i32 => u32, i64 => u64, i128 => u128, isize => usize);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_option_bound() {
let a = OptionBound::from(1);
let b = OptionBound::from(2);
let c = OptionBound::from(None);
assert_eq!(a.unbound(), Some(1));
assert_eq!(b.unbound(), Some(2));
assert_eq!(c.unbound(), None);
assert_eq!(a.partial_cmp(&b), Some(Ordering::Less));
assert_eq!(b.partial_cmp(&a), Some(Ordering::Greater));
assert_eq!(a.partial_cmp(&c), Some(Ordering::Less));
assert_eq!(c.partial_cmp(&a), Some(Ordering::Greater));
assert_eq!(c.partial_cmp(&c), Some(Ordering::Equal));
assert_eq!(a.partial_cmp(&2), Some(Ordering::Less));
assert_eq!(b.partial_cmp(&2), Some(Ordering::Equal));
assert_eq!(c.partial_cmp(&2), Some(Ordering::Greater));
assert_eq!(a, 1);
assert_eq!(b, 2);
assert_eq!(c, None);
assert_eq!(a, Some(1));
assert_eq!(b, Some(2));
}
#[test]
fn test_u8() {
let a = 1u8;
let b = 2u8;
assert_eq!(a.add_if_enclosed_by(1, b), Some(2));
assert_eq!(a.sub_if_enclosed_by(1, b), None);
assert_eq!(a.distance(b), 1);
assert_eq!(a.inc_if_lt(b), Some(2));
assert_eq!(a.dec_if_gt(b), None);
}
#[test]
fn test_i8() {
let a = -1i8;
let b = 1i8;
assert_eq!(a.add_if_enclosed_by(2, b), Some(1));
assert_eq!(a.sub_if_enclosed_by(1, b), None);
assert_eq!(a.distance(b), 2);
assert_eq!(a.inc_if_lt(b), Some(0));
assert_eq!(a.dec_if_gt(b), None);
}
}