1use core::cmp::Ordering;
23use num_traits::{One, PrimInt, Unsigned};
24
25pub trait Bound<T: Sized>: From<Option<T>> + Copy {
58 fn unbound(self) -> Option<T>;
62}
63
64pub trait Numerated: Copy + Sized + Ord + Eq {
70 type Distance: PrimInt + Unsigned;
72 type Bound: Bound<Self>;
75 fn add_if_enclosed_by(self, num: Self::Distance, other: Self) -> Option<Self>;
82 fn sub_if_enclosed_by(self, num: Self::Distance, other: Self) -> Option<Self>;
89 fn distance(self, other: Self) -> Self::Distance;
98 fn inc_if_lt(self, other: Self) -> Option<Self> {
100 self.add_if_enclosed_by(Self::Distance::one(), other)
101 }
102 fn dec_if_gt(self, other: Self) -> Option<Self> {
104 self.sub_if_enclosed_by(Self::Distance::one(), other)
105 }
106 fn enclosed_by(self, a: &Self, b: &Self) -> bool {
108 self <= *a.max(b) && self >= *a.min(b)
109 }
110}
111
112#[derive(Clone, Copy, Debug, PartialEq, Eq, derive_more::From)]
114pub struct OptionBound<T>(Option<T>);
115
116impl<T> From<T> for OptionBound<T> {
117 fn from(value: T) -> Self {
118 Some(value).into()
119 }
120}
121
122impl<T: Copy> Bound<T> for OptionBound<T> {
123 fn unbound(self) -> Option<T> {
124 self.0
125 }
126}
127
128impl<T> PartialOrd for OptionBound<T>
129where
130 T: PartialOrd,
131{
132 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
133 match (self.0.as_ref(), other.0.as_ref()) {
134 (None, None) => Some(Ordering::Equal),
135 (None, Some(_)) => Some(Ordering::Greater),
136 (Some(_), None) => Some(Ordering::Less),
137 (Some(a), Some(b)) => a.partial_cmp(b),
138 }
139 }
140}
141
142impl<T> Ord for OptionBound<T>
143where
144 T: Ord,
145{
146 fn cmp(&self, other: &Self) -> Ordering {
147 match (self.0.as_ref(), other.0.as_ref()) {
148 (None, None) => Ordering::Equal,
149 (None, Some(_)) => Ordering::Greater,
150 (Some(_), None) => Ordering::Less,
151 (Some(a), Some(b)) => a.cmp(b),
152 }
153 }
154}
155
156impl<T> PartialEq<T> for OptionBound<T>
157where
158 T: PartialEq,
159{
160 fn eq(&self, other: &T) -> bool {
161 self.0.as_ref().map(|a| a.eq(other)).unwrap_or(false)
162 }
163}
164
165impl<T> PartialEq<Option<T>> for OptionBound<T>
166where
167 T: PartialEq,
168{
169 fn eq(&self, other: &Option<T>) -> bool {
170 self.0 == *other
171 }
172}
173
174impl<T> PartialOrd<T> for OptionBound<T>
175where
176 T: PartialOrd,
177{
178 fn partial_cmp(&self, other: &T) -> Option<Ordering> {
179 self.0
180 .as_ref()
181 .map(|a| a.partial_cmp(other))
182 .unwrap_or(Some(Ordering::Greater))
183 }
184}
185
186macro_rules! impl_for_unsigned {
187 ($($t:ty)*) => ($(
188 impl Numerated for $t {
189 type Distance = $t;
190 type Bound = OptionBound<$t>;
191 fn add_if_enclosed_by(self, num: Self::Distance, other: Self) -> Option<Self> {
192 self.checked_add(num).and_then(|res| res.enclosed_by(&self, &other).then_some(res))
193 }
194 fn sub_if_enclosed_by(self, num: Self::Distance, other: Self) -> Option<Self> {
195 self.checked_sub(num).and_then(|res| res.enclosed_by(&self, &other).then_some(res))
196 }
197 fn distance(self, other: Self) -> $t {
198 self.abs_diff(other)
199 }
200 }
201 )*)
202}
203
204impl_for_unsigned!(u8 u16 u32 u64 u128 usize);
205
206macro_rules! impl_for_signed {
207 ($($s:ty => $u:ty),*) => {
208 $(
209 impl Numerated for $s {
210 type Distance = $u;
211 type Bound = OptionBound<$s>;
212 fn add_if_enclosed_by(self, num: Self::Distance, other: Self) -> Option<Self> {
213 let res = (self as $u).wrapping_add(num) as $s;
214 res.enclosed_by(&self, &other).then_some(res)
215 }
216 fn sub_if_enclosed_by(self, num: Self::Distance, other: Self) -> Option<Self> {
217 let res = (self as $u).wrapping_sub(num) as $s;
218 res.enclosed_by(&self, &other).then_some(res)
219 }
220 fn distance(self, other: Self) -> $u {
221 self.abs_diff(other)
222 }
223 }
224 )*
225 };
226}
227
228impl_for_signed!(i8 => u8, i16 => u16, i32 => u32, i64 => u64, i128 => u128, isize => usize);
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233
234 #[test]
235 fn test_option_bound() {
236 let a = OptionBound::from(1);
237 let b = OptionBound::from(2);
238 let c = OptionBound::from(None);
239 assert_eq!(a.unbound(), Some(1));
240 assert_eq!(b.unbound(), Some(2));
241 assert_eq!(c.unbound(), None);
242 assert_eq!(a.partial_cmp(&b), Some(Ordering::Less));
243 assert_eq!(b.partial_cmp(&a), Some(Ordering::Greater));
244 assert_eq!(a.partial_cmp(&c), Some(Ordering::Less));
245 assert_eq!(c.partial_cmp(&a), Some(Ordering::Greater));
246 assert_eq!(c.partial_cmp(&c), Some(Ordering::Equal));
247 assert_eq!(a.partial_cmp(&2), Some(Ordering::Less));
248 assert_eq!(b.partial_cmp(&2), Some(Ordering::Equal));
249 assert_eq!(c.partial_cmp(&2), Some(Ordering::Greater));
250 assert_eq!(a, 1);
251 assert_eq!(b, 2);
252 assert_eq!(c, None);
253 assert_eq!(a, Some(1));
254 assert_eq!(b, Some(2));
255 }
256
257 #[test]
258 fn test_u8() {
259 let a = 1u8;
260 let b = 2u8;
261 assert_eq!(a.add_if_enclosed_by(1, b), Some(2));
262 assert_eq!(a.sub_if_enclosed_by(1, b), None);
263 assert_eq!(a.distance(b), 1);
264 assert_eq!(a.inc_if_lt(b), Some(2));
265 assert_eq!(a.dec_if_gt(b), None);
266 }
267
268 #[test]
269 fn test_i8() {
270 let a = -1i8;
271 let b = 1i8;
272 assert_eq!(a.add_if_enclosed_by(2, b), Some(1));
273 assert_eq!(a.sub_if_enclosed_by(1, b), None);
274 assert_eq!(a.distance(b), 2);
275 assert_eq!(a.inc_if_lt(b), Some(0));
276 assert_eq!(a.dec_if_gt(b), None);
277 }
278}