ord_by_set/order.rs
1use core::cmp::Ordering;
2
3/// A trait representing the capability of taking two items and ordering them.
4///
5/// An orderer *is* allowed to have two "equal" values which are not actually equal,
6/// but can be considered loosely equal. This is similar to javascript's `==` operator,
7/// while [`Ord`] would be equivelant to javascript's `===` operator.
8///
9/// For example, if you had an enum which allowed both strings and numbers:
10///
11/// ```
12/// enum Val {
13/// String(String),
14/// Num(i32),
15/// }
16/// ```
17///
18/// You *could* allow `Val::String("3")` to be loosely equivelant to `Val::Num(3)`, while still
19/// having them be distinct values. Then if the following operation is performed:
20///
21/// ```
22/// # enum Val {
23/// # String(String),
24/// # Num(i32),
25/// # }
26/// #
27/// use ord_by_set::{OrdBySet, Order};
28/// use std::cmp::Ordering;
29///
30/// #[derive(Default)]
31/// struct LooseOrder;
32///
33/// impl Order<Val> for LooseOrder {
34/// fn order_of(&self, left: &Val, right: &Val) -> Ordering {
35/// match (left, right) {
36/// (Val::String(left), Val::String(right)) => left.cmp(right),
37/// (Val::Num(left), Val::Num(right)) => left.cmp(right),
38///
39/// (Val::String(left), Val::Num(right)) => left.parse::<i32>()
40/// .unwrap()
41/// .cmp(right),
42/// (Val::Num(left), Val::String(right)) => left.cmp(&right.parse::<i32>().unwrap()),
43/// }
44/// }
45/// }
46///
47/// let totally_numbers = [
48/// Val::Num(100),
49/// Val::String("70".into()),
50/// Val::Num(70),
51/// Val::String("30".into()),
52/// ];
53/// let ord = OrdBySet::new_with_order(LooseOrder).with_items(totally_numbers);
54///
55/// assert!(matches!(
56/// ord.get(&Val::Num(70)),
57/// Some([Val::Num(70), Val::String(num)] | [Val::String(num), Val::Num(70)])
58/// if num == "70"
59/// ));
60/// ```
61///
62/// ### Specification
63///
64/// The following behaviors must hold true in a proper `Order<T>` implementation:
65///
66/// * Exactly one of `a < b`, `a > b`, or `a == b` is true.
67/// * LessThan, Equals, and GreaterThan are all transitive. Which is to say that
68/// `a == b` and `b == c` implies `a == c`.
69///
70/// The easiest way to think about this is that `Order<T>` is a proper implementation of
71/// [`Ord`] for a subset of the type `T`, albeit with possibly alternate behavior to that
72/// of T's [`Ord`] itself, if such an implementation exists.
73///
74/// Failure to uphold this contract will result in unspecified (albeit safe/sound in the
75/// context of Rust's safety guarantees) behavior by [`OrdBySet`].
76///
77/// While not strictly required for a valid `Order` implementation, if you wish to use
78/// the `*_specific` methods in `OrdBySet<T>`, then it is expected that the set of
79/// comparison pairs (left, right) will return `Ordering::Equal` for all values of
80/// `left` and `right` where `PartialEq::eq(left, right)` returns `true`.
81///
82/// That is to say, the set of comparison pairs which are equivelant under `Order<T>`
83/// should be a superset of those equal under `PartialEq` if you choose to use those
84/// methods.
85pub trait Order<T> {
86 /// Takes two items and compares them, returning if the first is less than, equal to,
87 /// or greater than, the latter.
88 fn order_of(&self, left: &T, right: &T) -> Ordering;
89
90 /// Takes a slice of items and sorts them using the given order
91 fn sort_slice(&self, items: &mut [T]) {
92 items.sort_by(|left, right| self.order_of(&left, &right));
93 }
94}
95
96/// An ordering implementation that just defers to [`Ord`]
97#[derive(Default)]
98pub struct FullOrd;
99
100impl<T: Ord> Order<T> for FullOrd {
101 fn order_of(&self, left: &T, right: &T) -> Ordering {
102 left.cmp(right)
103 }
104}
105
106/// An implementation for closures to allow for ad-hoc ordering
107impl<T, OrderFn> Order<T> for OrderFn
108where
109 OrderFn: Fn(&T, &T) -> Ordering,
110{
111 fn order_of(&self, left: &T, right: &T) -> Ordering {
112 self(left, right)
113 }
114}