Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
263 changes: 262 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@
//! assert_eq!(25.rcmp(20..30), RangeOrdering::Inside);
//! assert_eq!(35.rcmp(20..30), RangeOrdering::Above);
//! ```
//!
//! This crates also provides the [`PartialRangeOrd`] trait on all types that implement [`PartialOrd`].
//! This traits exposes a [`partial_rcmp`](PartialRangeOrd::partial_rcmp) associated method:
//!
//! ```
//! use range_cmp::{PartialRangeOrd, RangeOrdering};
//! assert_eq!(15.partial_rcmp(20..30), Some(RangeOrdering::Below));
//! assert_eq!(25.partial_rcmp(20..30), Some(RangeOrdering::Inside));
//! assert_eq!(35.partial_rcmp(20..30), Some(RangeOrdering::Above));
//! ```

use std::borrow::Borrow;
use std::ops::{Bound, RangeBounds};
Expand Down Expand Up @@ -129,8 +139,58 @@ impl<T: Ord> RangeOrd for T {
}
}

/// Trait to provide the [`partial_rcmp`](PartialRangeOrd::partial_rcmp) method, which allows comparing
/// the type to a range. A blanket implementation is provided for all types that implement the
/// [`PartialOrd`] trait.
pub trait PartialRangeOrd {
/// Compare the value to a range of values. Returns whether the value is below, inside or above
/// the range.
///
/// ```
/// use range_cmp::{PartialRangeOrd, RangeOrdering};
/// assert_eq!(15.partial_rcmp(20..30), Some(RangeOrdering::Below));
/// assert_eq!(25.partial_rcmp(20..30), Some(RangeOrdering::Inside));
/// assert_eq!(35.partial_rcmp(20..30), Some(RangeOrdering::Above));
/// ```
fn partial_rcmp<R: RangeBounds<Self>, B: BorrowRange<Self, R>>(
&self,
range: B,
) -> Option<RangeOrdering>;
}

impl<T: PartialOrd> PartialRangeOrd for T {
fn partial_rcmp<R: RangeBounds<Self>, B: BorrowRange<Self, R>>(
&self,
range: B,
) -> Option<RangeOrdering> {
let range = range.borrow();

if range.contains(self) {
return Some(RangeOrdering::Inside);
}

if match range.start_bound() {
Bound::Included(key) => self < key,
Bound::Excluded(key) => self <= key,
_ => false,
} {
return Some(RangeOrdering::Below);
}

if match range.end_bound() {
Bound::Included(key) => self > key,
Bound::Excluded(key) => self >= key,
_ => false,
} {
return Some(RangeOrdering::Above);
}

None
}
}

#[cfg(test)]
mod tests {
mod rcmp_tests {
use super::*;

#[test]
Expand Down Expand Up @@ -358,3 +418,204 @@ mod tests {
assert_eq!(30.rcmp(&25..&15), RangeOrdering::Above);
}
}

#[cfg(test)]
mod partial_rcmp_tests {
use std::cmp::Ordering;

use super::*;

#[derive(PartialEq)]
struct Div(i32);

impl PartialOrd for Div {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
let a_s = self.0.abs();
let a_o = other.0.abs();

match a_s.cmp(&a_o) {
Ordering::Less if a_o % a_s == 0 => Some(std::cmp::Ordering::Less),
Ordering::Greater if a_s % a_o == 0 => Some(std::cmp::Ordering::Greater),
Ordering::Equal => Some(std::cmp::Ordering::Equal),
_ => None,
}
}
}

#[test]
fn range_full() {
// 1 is an integer
assert_eq!(Div(1).partial_rcmp(..), Some(RangeOrdering::Inside));
}

#[test]
fn range_from() {
// 1 is a multiple of 1
assert_eq!(Div(1).partial_rcmp(Div(1)..), Some(RangeOrdering::Inside));
assert_eq!(Div(1).partial_rcmp(&Div(1)..), Some(RangeOrdering::Inside));

// 1 is below the multiples of 2
assert_eq!(Div(1).partial_rcmp(Div(2)..), Some(RangeOrdering::Below));
assert_eq!(Div(1).partial_rcmp(&Div(2)..), Some(RangeOrdering::Below));

// 2 is uncomparable with the multiples of 3
assert_eq!(Div(2).partial_rcmp(Div(3)..), None);
assert_eq!(Div(2).partial_rcmp(&Div(3)..), None);
}

#[test]
fn range_to() {
// All divisors of 2 divide 4
assert_eq!(Div(4).partial_rcmp(..Div(2)), Some(RangeOrdering::Above));
assert_eq!(Div(4).partial_rcmp(..&Div(2)), Some(RangeOrdering::Above));

// 1 is a divisor of 2
assert_eq!(Div(1).partial_rcmp(..Div(2)), Some(RangeOrdering::Inside));
assert_eq!(Div(1).partial_rcmp(..&Div(2)), Some(RangeOrdering::Inside));

// 3 is uncomparable with the divisors of 10
assert_eq!(Div(3).partial_rcmp(..Div(10)), None);
assert_eq!(Div(3).partial_rcmp(..&Div(10)), None);
}

#[test]
fn range() {
// 3 is a multiple of all divisors of 3
assert_eq!(
Div(3).partial_rcmp(Div(1)..Div(3)),
Some(RangeOrdering::Above)
);
assert_eq!(
Div(3).partial_rcmp(&Div(1)..&Div(3)),
Some(RangeOrdering::Above)
);

// 6 is a multiple of 2 and a divisor of 12
assert_eq!(
Div(6).partial_rcmp(Div(2)..Div(12)),
Some(RangeOrdering::Inside)
);
assert_eq!(
Div(6).partial_rcmp(&Div(2)..&Div(12)),
Some(RangeOrdering::Inside)
);

// 2 divides all multiples of 4 that divide 8
assert_eq!(
Div(2).partial_rcmp(Div(4)..Div(8)),
Some(RangeOrdering::Below)
);
assert_eq!(
Div(2).partial_rcmp(&Div(4)..&Div(8)),
Some(RangeOrdering::Below)
);

// 3 is uncomparable with the multiples of 4 which are divisors of 12
assert_eq!(Div(3).partial_rcmp(Div(4)..Div(12)), None);
assert_eq!(Div(3).partial_rcmp(&Div(4)..&Div(12)), None);
}

#[test]
fn range_inclusive() {
// 6 is a multiple of all divisors of 3
assert_eq!(
Div(6).partial_rcmp(Div(1)..=Div(3)),
Some(RangeOrdering::Above)
);
assert_eq!(
Div(6).partial_rcmp(&Div(1)..=&Div(3)),
Some(RangeOrdering::Above)
);

// 6 is a multiple of 6 and a divisor of 6
assert_eq!(
Div(6).partial_rcmp(Div(6)..=Div(6)),
Some(RangeOrdering::Inside)
);
assert_eq!(
Div(6).partial_rcmp(&Div(6)..=&Div(6)),
Some(RangeOrdering::Inside)
);

// 2 divides all multiples of 4 that divide 8
assert_eq!(
Div(2).partial_rcmp(Div(4)..=Div(8)),
Some(RangeOrdering::Below)
);
assert_eq!(
Div(2).partial_rcmp(&Div(4)..=&Div(8)),
Some(RangeOrdering::Below)
);

// 3 is uncomparable with the multiples of 4 which are divisors of 12
assert_eq!(Div(3).partial_rcmp(Div(4)..=Div(12)), None);
assert_eq!(Div(3).partial_rcmp(&Div(4)..=&Div(12)), None);
}

#[test]
fn range_to_inclusive() {
// All divisors of 2 divide 4
assert_eq!(Div(4).partial_rcmp(..=Div(2)), Some(RangeOrdering::Above));
assert_eq!(Div(4).partial_rcmp(..=&Div(2)), Some(RangeOrdering::Above));

// 1 is a divisor of 2
assert_eq!(Div(1).partial_rcmp(..=Div(2)), Some(RangeOrdering::Inside));
assert_eq!(Div(1).partial_rcmp(..=&Div(2)), Some(RangeOrdering::Inside));

// 3 is uncomparable with the divisors of 10
assert_eq!(Div(3).partial_rcmp(..=Div(10)), None);
assert_eq!(Div(3).partial_rcmp(..=&Div(10)), None);
}

#[test]
fn bounds_full() {
// 1 is an integer
let bounds: (Bound<Div>, Bound<Div>) = (Bound::Unbounded, Bound::Unbounded);
assert_eq!(Div(1).partial_rcmp(bounds), Some(RangeOrdering::Inside));
}

#[test]
fn bounds_from() {
// 1 a multiple of 1
let bounds = (Bound::Included(Div(1)), Bound::Unbounded);
assert_eq!(Div(1).partial_rcmp(bounds), Some(RangeOrdering::Inside));

let bounds = (Bound::Included(&Div(1)), Bound::Unbounded);
assert_eq!(Div(1).partial_rcmp(bounds), Some(RangeOrdering::Inside));

// 1 divides all multiples of 2
let bounds = (Bound::Included(Div(2)), Bound::Unbounded);
assert_eq!(Div(1).partial_rcmp(bounds), Some(RangeOrdering::Below));

let bounds = (Bound::Included(&Div(2)), Bound::Unbounded);
assert_eq!(Div(1).partial_rcmp(bounds), Some(RangeOrdering::Below));

// 2 is uncomparable with the multiples of 3
let bounds = (Bound::Included(Div(3)), Bound::Unbounded);
assert_eq!(Div(2).partial_rcmp(bounds), None);

let bounds = (Bound::Included(&Div(3)), Bound::Unbounded);
assert_eq!(Div(2).partial_rcmp(bounds), None);
}

#[test]
fn bounds_to() {}

#[test]
fn bounds() {}

#[test]
fn bounds_inclusive() {}

#[test]
fn bounds_to_inclusive() {}

#[test]
fn bounds_exclusive_inclusive() {}

#[test]
fn bounds_as_reference() {}

#[test]
fn empty_ranges() {}
}