1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
use std::{cmp::Ordering, ops::Range};

pub enum Contains {
    Full,
    Partial,
}

pub trait RangeExt<T> {
    fn merge(self, r: Range<T>) -> Option<Range<T>>;
    fn compare(&self, r: &Range<T>) -> Ordering;
    fn contains_range(&self, r: &Range<T>) -> Option<Contains>;
}

impl<T: Ord> RangeExt<T> for Range<T> {
    fn merge(self, r: Range<T>) -> Option<Range<T>> {
        let start_end = self.start.cmp(&r.end);
        let end_start = self.end.cmp(&r.start);
        match (start_end, end_start) {
            (Ordering::Less, Ordering::Less) | (Ordering::Greater, Ordering::Greater) => {
                return None
            }
            _ => {}
        };
        let starts = self.start.cmp(&r.start);
        let ends = self.end.cmp(&r.end);
        match (starts, ends) {
            (Ordering::Less | Ordering::Equal, Ordering::Greater | Ordering::Equal) => {
                Some(self.start..self.end)
            }
            (Ordering::Greater | Ordering::Equal, Ordering::Less | Ordering::Equal) => {
                Some(r.start..r.end)
            }
            (Ordering::Less, Ordering::Less) => Some(self.start..r.end),
            (Ordering::Greater, Ordering::Greater) => Some(r.start..self.end),
        }
    }

    fn compare(&self, r: &Range<T>) -> Ordering {
        self.start.cmp(&r.start).then_with(|| self.end.cmp(&r.end))
    }

    fn contains_range(&self, r: &Range<T>) -> Option<Contains> {
        let start_end = self.start.cmp(&r.end);
        let end_start = self.end.cmp(&r.start);
        match (start_end, end_start) {
            (Ordering::Less, Ordering::Less) | (Ordering::Greater, Ordering::Greater) => {
                return None
            }
            _ => {}
        };
        let starts = self.start.cmp(&r.start);
        let ends = self.end.cmp(&r.end);
        match (starts, ends) {
            (Ordering::Less | Ordering::Equal, Ordering::Greater | Ordering::Equal) => {
                Some(Contains::Full)
            }
            (Ordering::Greater | Ordering::Equal, Ordering::Less | Ordering::Equal) => {
                Some(Contains::Partial)
            }
            (Ordering::Less, Ordering::Less) => Some(Contains::Partial),
            (Ordering::Greater, Ordering::Greater) => Some(Contains::Partial),
        }
    }
}

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

    #[test]
    fn extended_bounds() {
        assert_eq!((0..4).merge(1..3).unwrap(), 0..4);
        assert_eq!((1..3).merge(0..4).unwrap(), 0..4);
        assert_eq!((-2..3).merge(0..4).unwrap(), -2..4);
        assert_eq!((1..5).merge(0..4).unwrap(), 0..5);
    }

    #[test]
    fn outside_of_bounds() {
        assert_eq!((0..2).merge(3..5), None);
        assert_eq!((3..5).merge(0..2), None);
    }

    #[test]
    fn edge_cases() {
        assert_eq!((0..2).merge(2..5).unwrap(), 0..5);
        assert_eq!((2..5).merge(0..2).unwrap(), 0..5);
    }
}