portion_rs/
ops.rs

1//! Operations defined on intervals.
2
3use crate::helpers::{LeftBound, RightBound};
4use crate::impls::Item;
5use crate::{Interval, IntervalType, Portion};
6use std::ops::{BitAnd, BitOr};
7
8/// Operations defined on interval-like things.
9pub trait Operations: Sized {
10    /// The return type used in implementations.
11    type Output;
12
13    /// Returns whether the interval is empty, regardless of it's actual type.
14    fn empty(&self) -> bool {
15        true
16    }
17
18    /// Returns whether the interval is atomic.
19    fn atomic(&self) -> bool {
20        true
21    }
22
23    /// Returns the intersection of two intervals, shorthand for `interval & interval`.
24    fn intersection(self, _other: Self::Output) -> Self::Output {
25        unimplemented!()
26    }
27
28    /// Returns the union of two intervals, shorthand for `interval | interval`.
29    fn union(self, _other: Self::Output) -> Self::Output {
30        unimplemented!()
31    }
32}
33
34impl<T: Item> Operations for Interval<T> {
35    type Output = Self;
36
37    fn empty(&self) -> bool {
38        use IntervalType::*;
39        match self.itype {
40            Open => self.lower >= self.upper,
41            Closed => self.lower > self.upper,
42            Empty => true,
43            Singleton => false,
44            OpenClosed => self.lower >= self.upper,
45            ClosedOpen => self.lower >= self.upper,
46        }
47    }
48
49    fn intersection(self, other: Self::Output) -> Self::Output {
50        self & other
51    }
52
53    fn union(self, other: Self::Output) -> Self::Output {
54        self | other
55    }
56}
57
58// Intersection.
59impl<T: Item> BitAnd for Interval<T> {
60    type Output = Self;
61
62    fn bitand(self, rhs: Self) -> Self::Output {
63        // Optimization.
64        if self.empty() || rhs.empty() {
65            return Portion::empty();
66        }
67
68        // Handle singletons first.
69        if self.singleton() {
70            if rhs.contains(self.lower()) {
71                return self;
72            } else {
73                return Portion::empty();
74            }
75        }
76
77        if rhs.singleton() {
78            if self.contains(rhs.lower.unwrap()) {
79                return rhs;
80            } else {
81                return Portion::empty();
82            }
83        }
84
85        // Optimization.
86        if self.upper < rhs.lower {
87            return Portion::empty();
88        }
89
90        // Match the bounds.
91        let left_bound = self.get_left_bound(&rhs);
92        let right_bound = self.get_right_bound(&rhs);
93
94        match left_bound {
95            LeftBound::Open(lower) => match right_bound {
96                RightBound::Open(upper) => Portion::open(lower, upper),
97                RightBound::Closed(upper) => Portion::openclosed(lower, upper),
98                RightBound::None => Portion::empty(),
99            },
100            LeftBound::Closed(lower) => match right_bound {
101                RightBound::Open(upper) => Portion::closedopen(lower, upper),
102                RightBound::Closed(upper) => Portion::closed(lower, upper),
103                RightBound::None => Portion::empty(),
104            },
105            LeftBound::None => Portion::empty(),
106        }
107    }
108}
109
110// Union.
111impl<T: Item> BitOr for Interval<T> {
112    type Output = Self;
113
114    fn bitor(self, rhs: Interval<T>) -> Self::Output {
115        if self.empty() {
116            return rhs;
117        }
118
119        if rhs.empty() {
120            return self;
121        }
122
123        // TODO: return a union of two intervals?
124        #[allow(clippy::suspicious_operation_groupings)]
125        if !self.singleton() && !rhs.singleton() && self.upper < rhs.lower {
126            return Portion::empty();
127        }
128
129        let left_val = self.get_lowest_val(&rhs);
130        let right_val = self.get_highest_val(&rhs);
131
132        match left_val {
133            LeftBound::Closed(lower) => match right_val {
134                RightBound::Closed(upper) => Portion::closed(lower, upper),
135                RightBound::Open(upper) => Portion::closedopen(lower, upper),
136                RightBound::None => unreachable!(),
137            },
138            LeftBound::Open(lower) => match right_val {
139                RightBound::Closed(upper) => Portion::openclosed(lower, upper),
140                RightBound::Open(upper) => Portion::open(lower, upper),
141                RightBound::None => unreachable!(),
142            },
143            LeftBound::None => unreachable!(),
144        }
145    }
146}