Skip to main content

yui_core/ext/
range.rs

1//! [`RangeExt`]: shift the endpoints of a range, and [`empty_range`].
2
3use std::ops::{Add, Neg, Range, RangeInclusive, Sub};
4
5/// The empty `RangeInclusive<isize>`, for a grading support with no elements.
6/// `start > end`, so it yields nothing and `contains` is always false.
7#[allow(clippy::reversed_empty_ranges)] // deliberately reversed: that is what makes it empty.
8pub fn empty_range() -> RangeInclusive<isize> {
9    0 ..= -1
10}
11
12/// Shift the endpoints of a [`Range`] or [`RangeInclusive`] by independent
13/// left/right offsets.
14pub trait RangeExt
15where Self::Idx: Copy, Self: Sized {
16    type Idx;
17    fn mv(&self, l: Self::Idx, r: Self::Idx) -> Self;
18    fn shift(&self, a: Self::Idx) -> Self {
19        self.mv(a, a)
20    }
21}
22
23impl<Idx> RangeExt for Range<Idx>
24where Idx: Copy + Add<Output = Idx> + Sub<Output = Idx> + Neg<Output = Idx> {
25    type Idx = Idx;
26
27    fn mv(&self, l: Self::Idx, r: Self::Idx) -> Self {
28        (self.start + l) .. (self.end + r)
29    }
30}
31
32impl<Idx> RangeExt for RangeInclusive<Idx>
33where Idx: Copy + Add<Output = Idx> + Sub<Output = Idx> + Neg<Output = Idx> {
34    type Idx = Idx;
35
36    fn mv(&self, l: Self::Idx, r: Self::Idx) -> Self {
37        (*self.start() + l) ..= (*self.end() + r)
38    }
39}
40
41#[cfg(test)]
42mod tests {
43    use crate::ext::{empty_range, RangeExt};
44
45    #[test]
46    fn empty() {
47        let r = empty_range();
48        assert!(r.is_empty());
49        assert_eq!(r.clone().count(), 0);
50        assert!(!r.contains(&0));
51    }
52
53    #[test]
54    fn range() {
55        let r = -1 .. 3;
56        assert_eq!(r.mv(2, 3), 1 .. 6);
57        assert_eq!(r.shift(2), 1 .. 5);
58    }
59
60    #[test]
61    fn range_incl() {
62        let r = -1 ..= 3;
63        assert_eq!(r.mv(2, 3), 1 ..= 6);
64        assert_eq!(r.shift(2), 1 ..= 5);
65    }
66}