pub use std::cmp::Ordering;
pub type DynOrder<A> = Box<dyn Fn(&A, &A) -> Ordering + Send + Sync>;
pub mod ordering {
use super::Ordering;
pub fn reverse(o: Ordering) -> Ordering {
o.reverse()
}
pub fn combine(first: Ordering, second: Ordering) -> Ordering {
match first {
Ordering::Equal => second,
other => other,
}
}
pub fn match_<A>(
o: Ordering,
on_less: impl FnOnce() -> A,
on_equal: impl FnOnce() -> A,
on_greater: impl FnOnce() -> A,
) -> A {
match o {
Ordering::Less => on_less(),
Ordering::Equal => on_equal(),
Ordering::Greater => on_greater(),
}
}
}
#[allow(clippy::module_inception)]
pub mod order {
use super::{DynOrder, Ordering};
use std::time::Duration;
pub fn duration() -> DynOrder<Duration> {
Box::new(|a: &Duration, b: &Duration| a.cmp(b))
}
pub fn string() -> DynOrder<String> {
Box::new(|a: &String, b: &String| a.cmp(b))
}
pub fn number_f64() -> DynOrder<f64> {
Box::new(|a: &f64, b: &f64| a.partial_cmp(b).unwrap_or(Ordering::Equal))
}
pub fn number_i64() -> DynOrder<i64> {
Box::new(|a: &i64, b: &i64| a.cmp(b))
}
pub fn number_u64() -> DynOrder<u64> {
Box::new(|a: &u64, b: &u64| a.cmp(b))
}
pub fn number_usize() -> DynOrder<usize> {
Box::new(|a: &usize, b: &usize| a.cmp(b))
}
pub fn reverse<A: 'static>(ord: DynOrder<A>) -> DynOrder<A> {
Box::new(move |a, b| ord(b, a))
}
pub fn combine<A: 'static>(first: DynOrder<A>, second: DynOrder<A>) -> DynOrder<A> {
Box::new(move |a, b| super::ordering::combine(first(a, b), second(a, b)))
}
pub fn map_input<A: 'static, B: 'static>(
ord: DynOrder<A>,
f: impl Fn(&B) -> A + Send + Sync + 'static,
) -> DynOrder<B> {
Box::new(move |a, b| ord(&f(a), &f(b)))
}
pub fn less_than<A>(ord: &DynOrder<A>, a: &A, b: &A) -> bool {
ord(a, b) == Ordering::Less
}
pub fn less_than_or_equal_to<A>(ord: &DynOrder<A>, a: &A, b: &A) -> bool {
ord(a, b) != Ordering::Greater
}
pub fn greater_than<A>(ord: &DynOrder<A>, a: &A, b: &A) -> bool {
ord(a, b) == Ordering::Greater
}
pub fn greater_than_or_equal_to<A>(ord: &DynOrder<A>, a: &A, b: &A) -> bool {
ord(a, b) != Ordering::Less
}
pub fn min<A: Clone>(ord: &DynOrder<A>, a: A, b: A) -> A {
if ord(&a, &b) != Ordering::Greater {
a
} else {
b
}
}
pub fn max<A: Clone>(ord: &DynOrder<A>, a: A, b: A) -> A {
if ord(&a, &b) != Ordering::Less { a } else { b }
}
pub fn clamp<A: Clone>(ord: &DynOrder<A>, value: A, minimum: A, maximum: A) -> A {
if ord(&value, &minimum) == Ordering::Less {
minimum
} else if ord(&value, &maximum) == Ordering::Greater {
maximum
} else {
value
}
}
pub fn between<A>(ord: &DynOrder<A>, value: &A, minimum: &A, maximum: &A) -> bool {
ord(value, minimum) != Ordering::Less && ord(value, maximum) != Ordering::Greater
}
pub fn sort_with<A: Clone>(ord: &DynOrder<A>, mut arr: Vec<A>) -> Vec<A> {
arr.sort_by(|a, b| ord(a, b));
arr
}
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
use std::time::Duration;
mod order_duration {
use super::*;
#[test]
fn max_matches_std_duration_ordering() {
let ord = order::duration();
let a = Duration::from_millis(10);
let b = Duration::from_millis(20);
assert_eq!(order::max(&ord, a, b), b);
}
}
mod ordering_reverse {
use super::*;
#[rstest]
#[case::less(Ordering::Less, Ordering::Greater)]
#[case::equal(Ordering::Equal, Ordering::Equal)]
#[case::greater(Ordering::Greater, Ordering::Less)]
fn reverses_all_variants(#[case] input: Ordering, #[case] expected: Ordering) {
assert_eq!(ordering::reverse(input), expected);
}
}
mod ordering_combine {
use super::*;
#[test]
fn equal_defers_to_second() {
assert_eq!(
ordering::combine(Ordering::Equal, Ordering::Less),
Ordering::Less
);
assert_eq!(
ordering::combine(Ordering::Equal, Ordering::Greater),
Ordering::Greater
);
}
#[test]
fn less_ignores_second() {
assert_eq!(
ordering::combine(Ordering::Less, Ordering::Greater),
Ordering::Less
);
}
#[test]
fn greater_ignores_second() {
assert_eq!(
ordering::combine(Ordering::Greater, Ordering::Less),
Ordering::Greater
);
}
}
mod ordering_match {
use super::*;
#[rstest]
#[case::less(Ordering::Less, "less")]
#[case::equal(Ordering::Equal, "equal")]
#[case::greater(Ordering::Greater, "greater")]
fn dispatches_to_correct_branch(#[case] o: Ordering, #[case] expected: &str) {
let result = ordering::match_(o, || "less", || "equal", || "greater");
assert_eq!(result, expected);
}
}
mod order_string {
use super::*;
#[test]
fn lexicographic_order_less() {
let ord = order::string();
assert_eq!(ord(&"a".to_string(), &"b".to_string()), Ordering::Less);
}
#[test]
fn lexicographic_order_equal() {
let ord = order::string();
assert_eq!(ord(&"x".to_string(), &"x".to_string()), Ordering::Equal);
}
#[test]
fn lexicographic_order_greater() {
let ord = order::string();
assert_eq!(ord(&"b".to_string(), &"a".to_string()), Ordering::Greater);
}
}
mod order_number_f64 {
use super::*;
#[test]
fn smaller_float_is_less() {
let ord = order::number_f64();
assert_eq!(ord(&1.0_f64, &2.0_f64), Ordering::Less);
}
#[test]
fn equal_floats_are_equal() {
let ord = order::number_f64();
assert_eq!(ord(&3.5_f64, &3.5_f64), Ordering::Equal);
}
#[test]
fn larger_float_is_greater() {
let ord = order::number_f64();
assert_eq!(ord(&5.0_f64, &2.0_f64), Ordering::Greater);
}
#[test]
fn nan_does_not_panic() {
let ord = order::number_f64();
let _ = ord(&f64::NAN, &1.0_f64);
}
}
mod order_reverse {
use super::*;
#[test]
fn reversed_i64_order_has_less_become_greater() {
let ord = order::reverse(order::number_i64());
assert_eq!(ord(&1_i64, &2_i64), Ordering::Greater);
}
#[test]
fn reversed_order_equal_stays_equal() {
let ord = order::reverse(order::number_i64());
assert_eq!(ord(&5_i64, &5_i64), Ordering::Equal);
}
}
mod order_combine {
use super::*;
#[test]
fn when_first_is_equal_uses_second() {
let by_len = order::map_input(order::number_usize(), |s: &String| s.len());
let by_lex = order::string();
let combined = order::combine(by_len, by_lex);
assert_eq!(
combined(&"ab".to_string(), &"ba".to_string()),
Ordering::Less
);
}
#[test]
fn when_first_is_not_equal_ignores_second() {
let short = "a".to_string();
let longer = "bb".to_string();
let by_len = order::map_input(order::number_usize(), |s: &String| s.len());
let by_lex_rev = order::reverse(order::string());
let combined = order::combine(by_len, by_lex_rev);
assert_eq!(combined(&short, &longer), Ordering::Less);
}
}
mod order_map_input {
use super::*;
#[test]
fn contramap_on_string_length() {
let by_len: DynOrder<String> = order::map_input(order::number_usize(), |s: &String| s.len());
assert_eq!(by_len(&"a".to_string(), &"bb".to_string()), Ordering::Less);
assert_eq!(
by_len(&"aa".to_string(), &"bb".to_string()),
Ordering::Equal
);
}
}
mod order_min_max {
use super::*;
#[test]
fn min_returns_smaller() {
let ord = order::number_i64();
assert_eq!(order::min(&ord, 3_i64, 7_i64), 3);
}
#[test]
fn min_when_equal_returns_first() {
let ord = order::number_i64();
assert_eq!(order::min(&ord, 5_i64, 5_i64), 5);
}
#[test]
fn max_returns_larger() {
let ord = order::number_i64();
assert_eq!(order::max(&ord, 3_i64, 7_i64), 7);
}
}
mod order_clamp {
use super::*;
#[test]
fn value_below_minimum_clamped_to_minimum() {
let ord = order::number_i64();
assert_eq!(order::clamp(&ord, -5_i64, 0_i64, 100_i64), 0);
}
#[test]
fn value_above_maximum_clamped_to_maximum() {
let ord = order::number_i64();
assert_eq!(order::clamp(&ord, 200_i64, 0_i64, 100_i64), 100);
}
#[test]
fn value_within_range_unchanged() {
let ord = order::number_i64();
assert_eq!(order::clamp(&ord, 50_i64, 0_i64, 100_i64), 50);
}
#[rstest]
#[case::at_min(0_i64, 0)]
#[case::at_max(100_i64, 100)]
#[case::in_middle(50_i64, 50)]
fn clamp_at_boundaries(#[case] value: i64, #[case] expected: i64) {
let ord = order::number_i64();
assert_eq!(order::clamp(&ord, value, 0_i64, 100_i64), expected);
}
}
mod order_between {
use super::*;
#[test]
fn value_in_range_returns_true() {
let ord = order::number_i64();
assert!(order::between(&ord, &50_i64, &0_i64, &100_i64));
}
#[test]
fn value_at_minimum_returns_true() {
let ord = order::number_i64();
assert!(order::between(&ord, &0_i64, &0_i64, &100_i64));
}
#[test]
fn value_at_maximum_returns_true() {
let ord = order::number_i64();
assert!(order::between(&ord, &100_i64, &0_i64, &100_i64));
}
#[test]
fn value_below_range_returns_false() {
let ord = order::number_i64();
assert!(!order::between(&ord, &-1_i64, &0_i64, &100_i64));
}
#[test]
fn value_above_range_returns_false() {
let ord = order::number_i64();
assert!(!order::between(&ord, &101_i64, &0_i64, &100_i64));
}
}
mod order_sort_with {
use super::*;
#[test]
fn sort_integers_ascending() {
let ord = order::number_i64();
let result = order::sort_with(&ord, vec![3_i64, 1, 4, 1, 5, 9, 2]);
assert_eq!(result, vec![1, 1, 2, 3, 4, 5, 9]);
}
#[test]
fn sort_integers_descending_via_reverse() {
let ord = order::reverse(order::number_i64());
let result = order::sort_with(&ord, vec![3_i64, 1, 4]);
assert_eq!(result, vec![4, 3, 1]);
}
#[test]
fn sort_empty_vec_returns_empty() {
let ord = order::number_i64();
assert_eq!(order::sort_with(&ord, vec![]), Vec::<i64>::new());
}
#[test]
fn sort_single_element_returns_same() {
let ord = order::number_i64();
assert_eq!(order::sort_with(&ord, vec![42_i64]), vec![42]);
}
}
mod order_predicates {
use super::*;
#[test]
fn less_than_true_when_a_less() {
let ord = order::number_i64();
assert!(order::less_than(&ord, &1_i64, &2_i64));
}
#[test]
fn less_than_false_when_equal() {
let ord = order::number_i64();
assert!(!order::less_than(&ord, &2_i64, &2_i64));
}
#[test]
fn less_than_false_when_a_greater() {
let ord = order::number_i64();
assert!(!order::less_than(&ord, &3_i64, &2_i64));
}
#[test]
fn greater_than_true_when_a_greater() {
let ord = order::number_i64();
assert!(order::greater_than(&ord, &3_i64, &2_i64));
}
#[test]
fn less_than_or_equal_true_when_equal() {
let ord = order::number_i64();
assert!(order::less_than_or_equal_to(&ord, &5_i64, &5_i64));
}
}
}