use anyhow::{Result, bail};
use std::cmp::Ordering;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Side {
value: usize,
is_negative: bool,
}
impl std::fmt::Display for Side {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
if self.is_negative {
write!(f, "-{}", self.value + 1)
} else {
write!(f, "{}", self.value + 1)
}
}
}
impl Side {
#[inline(always)]
pub fn with_pos_value(value: usize) -> Self {
Self {
value,
is_negative: false,
}
}
#[inline(always)]
pub fn with_neg_value(value: usize) -> Self {
Self {
value,
is_negative: true,
}
}
#[inline(always)]
pub fn with_pos_inf() -> Self {
Self {
value: Self::max_right(),
is_negative: false,
}
}
#[inline(always)]
pub fn value_unchecked(&self) -> usize {
self.value
}
#[inline(always)]
#[must_use]
pub fn value(&self) -> (bool, usize) {
(self.is_negative, self.value)
}
#[inline(always)]
pub const fn max_right() -> usize {
usize::MAX
}
#[inline(always)]
pub fn is_negative(&self) -> bool {
self.is_negative
}
pub fn from_str_left_bound(s: &str) -> Result<Self, anyhow::Error> {
Self::from_str(s, true)
}
pub fn from_str_right_bound(s: &str) -> Result<Self, anyhow::Error> {
Self::from_str(s, false)
}
fn from_str(s: &str, is_left_bound: bool) -> Result<Self, anyhow::Error> {
Ok(match s {
"" => Side {
value: if is_left_bound { 0 } else { Self::max_right() },
is_negative: false,
},
_ => {
let v = s
.parse::<isize>()
.or_else(|_| bail!("Not a number `{}`", s))?;
if v == 0 {
bail!("Zero is not a valid field");
}
if v > 0 {
Side {
value: usize::try_from(v.abs()).unwrap() - 1,
is_negative: false,
}
} else {
Side {
value: usize::try_from(v.abs()).unwrap() - 1,
is_negative: true,
}
}
}
})
}
}
impl PartialOrd for Side {
#[inline(always)]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
if self.is_negative ^ other.is_negative {
return None;
}
if self.is_negative {
Some(other.value.cmp(&self.value))
} else {
Some(self.value.cmp(&other.value))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_from_str_some() {
assert_eq!(
Side::from_str_left_bound("42").unwrap(),
Side {
value: 41,
is_negative: false
}
);
assert_eq!(
Side::from_str_left_bound("-7").unwrap(),
Side {
value: 6,
is_negative: true
}
);
}
#[test]
fn test_from_str_continue() {
assert_eq!(
Side::from_str_left_bound("").unwrap(),
Side {
value: 0,
is_negative: false
}
);
assert_eq!(
Side::from_str_right_bound("").unwrap(),
Side {
value: usize::MAX,
is_negative: false
}
);
}
#[test]
fn test_from_str_zero() {
assert!(Side::from_str_left_bound("0").is_err());
}
#[test]
fn test_from_str_invalid() {
assert!(Side::from_str_left_bound("abc").is_err());
assert!(Side::from_str_left_bound("4.2").is_err());
}
#[test]
fn test_partial_ord_same_sign() {
assert_eq!(
Side::from_str_left_bound("3")
.unwrap()
.partial_cmp(&Side::from_str_left_bound("5").unwrap()),
Some(Ordering::Less)
);
assert_eq!(
Side::from_str_left_bound("5")
.unwrap()
.partial_cmp(&Side::from_str_left_bound("3").unwrap()),
Some(Ordering::Greater)
);
assert_eq!(
Side::from_str_left_bound("7")
.unwrap()
.partial_cmp(&Side::from_str_left_bound("7").unwrap()),
Some(Ordering::Equal)
);
assert_eq!(
Side::from_str_left_bound("-2")
.unwrap()
.partial_cmp(&Side::from_str_left_bound("-1").unwrap()),
Some(Ordering::Less)
);
}
#[test]
fn test_partial_ord_different_sign() {
assert_eq!(
Side::from_str_left_bound("3")
.unwrap()
.partial_cmp(&Side::from_str_left_bound("-5").unwrap()),
None
);
assert_eq!(
Side::from_str_left_bound("-5")
.unwrap()
.partial_cmp(&Side::from_str_left_bound("3").unwrap()),
None
);
}
#[test]
fn test_eq_trait() {
assert_eq!(
Side::from_str_left_bound("1").unwrap(),
Side::from_str_left_bound("1").unwrap()
);
assert_eq!(
Side::from_str_left_bound("-1").unwrap(),
Side::from_str_left_bound("-1").unwrap()
);
assert_ne!(
Side::from_str_left_bound("1").unwrap(),
Side::from_str_left_bound("2").unwrap()
);
assert_ne!(
Side::from_str_left_bound("-1").unwrap(),
Side::from_str_left_bound("1").unwrap()
);
}
}