diff --git a/portion/interval.py b/portion/interval.py index 4b00512..c63f4af 100644 --- a/portion/interval.py +++ b/portion/interval.py @@ -239,6 +239,23 @@ def enclosure(self): """ return Interval.from_atomic(self.left, self.lower, self.upper, self.right) + @property + def measure(self): + """ + Return a measure (~ length) of the interval. + + Only valid for intervals with numeric bounds. + """ + if self.empty: + return 0 + if self.upper != inf and type(self.upper) not in (int, float): + raise TypeError("Interval bounds must be numeric") + if self.lower != -inf and type(self.lower) not in (int, float): + raise TypeError("Interval bounds must be numeric") + if self.upper == inf or self.lower == -inf: + return float("inf") + return sum([i.upper - i.lower for i in self._intervals]) + def replace( self, left=None, lower=None, upper=None, right=None, *, ignore_inf=True ): diff --git a/tests/test_interval.py b/tests/test_interval.py index d9850c0..668b1db 100644 --- a/tests/test_interval.py +++ b/tests/test_interval.py @@ -139,6 +139,31 @@ def test_enclosure(self): assert P.closed(0, 4) == (P.closed(0, 1) | P.closed(3, 4)).enclosure assert P.openclosed(0, 4) == (P.open(0, 1) | P.closed(3, 4)).enclosure + def test_measure(self): + assert P.empty().measure == 0 + assert P.singleton(1).measure == 0 + assert (P.singleton(-1) | (P.singleton(0) | P.singleton(1))).measure == 0 + + assert P.open(0, 1).measure == 1 + assert P.openclosed(0, 1).measure == 1 + assert P.closedopen(0, 1).measure == 1 + assert P.closed(0, 1).measure == 1 + + assert (P.open(0, 1) | P.open(3, 5)).measure == 3 + + assert (P.open(0, P.inf)).measure == float('inf') + assert (P.open(-P.inf, 0)).measure == float('inf') + assert (P.open(-P.inf, P.inf)).measure == float('inf') + + with pytest.raises(TypeError): + P.open('a', 'b').measure + + with pytest.raises(TypeError): + P.open('a', P.inf).measure + + with pytest.raises(TypeError): + P.open(-P.inf, 'b').measure + class TestIntervalReplace: def test_replace_bounds(self):