From 7ed0644aa4c5b5caeb6ed9fae712b5b117fe1edb Mon Sep 17 00:00:00 2001 From: Adarsh Krishnan Date: Tue, 4 Feb 2014 11:53:21 +0200 Subject: [PATCH 1/9] Add support for empty intervals --- intervals/__init__.py | 25 +++++++++++++++++++++++-- tests/test_operators.py | 17 +++++++++++++++++ tests/test_properties.py | 13 +++++++++++++ 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/intervals/__init__.py b/intervals/__init__.py index 5241270..a10995f 100644 --- a/intervals/__init__.py +++ b/intervals/__init__.py @@ -50,6 +50,9 @@ def canonicalize(interval, lower_inc=True, upper_inc=False): if not interval.discrete: raise TypeError('Only discrete ranges can be canonicalized') + if interval.empty: + return interval + lower, lower_inc = canonicalize_lower(interval, lower_inc) upper, upper_inc = canonicalize_upper(interval, upper_inc) @@ -307,6 +310,13 @@ def radius(self): def degenerate(self): return self.upper == self.lower + @property + def empty(self): + return ( + self.upper == self.lower and not + (self.lower_inc and self.upper_inc) + ) + @property def centre(self): return float((self.lower + self.upper)) / 2 @@ -365,16 +375,27 @@ def __and__(self, other): """ Defines the intersection operator """ + if self.upper < other.lower or other.upper < self.lower: + return self.__class__((0, 0)) if self.lower <= other.lower <= self.upper: - return self.__class__([ + intersection = self.__class__([ other.lower, other.upper if other.upper < self.upper else self.upper ]) + intersection.lower_inc = other.lower_inc + intersection.upper_inc = ( + other.upper_inc if other.upper < self.upper else self.upper_inc + ) elif self.lower <= other.upper <= self.upper: - return self.__class__([ + intersection = self.__class__([ other.lower if other.lower > self.lower else self.lower, other.upper ]) + intersection.lower_inc = ( + other.lower_inc if other.lower > self.lower else self.lower_inc + ) + intersection.upper_inc = other.upper_inc + return intersection class IntInterval(AbstractInterval): diff --git a/tests/test_operators.py b/tests/test_operators.py index 731dee7..e96c485 100644 --- a/tests/test_operators.py +++ b/tests/test_operators.py @@ -63,3 +63,20 @@ class TestDiscreteRangeComparison(object): )) def test_eq_operator(self, interval, interval2): assert IntInterval(interval) == IntInterval(interval2) + + +class TestBinaryOperators(object): + @mark.parametrize(('interval1', 'interval2', 'result', 'empty'), ( + ((2, 3), (3, 4), (3, 3), True), + ((2, 3), [3, 4], '[3, 3)', True), + ((2, 5), (3, 10), (3, 5), False), + ('(2, 3]', '[3, 4)', [3, 3], False), + ('(2, 10]', '[3, 40]', [3, 10], False), + ((2, 10), (3, 8), (3, 8), False), + )) + def test_and_operator(self, interval1, interval2, result, empty): + assert ( + IntInterval(interval1) & IntInterval(interval2) == + IntInterval(result) + ) + assert IntInterval(result).empty == empty diff --git a/tests/test_properties.py b/tests/test_properties.py index cf8c4a5..fb97514 100644 --- a/tests/test_properties.py +++ b/tests/test_properties.py @@ -70,6 +70,19 @@ def test_open(self, number_range, is_open): def test_closed(self, number_range, is_closed): assert IntInterval(number_range).closed == is_closed + @mark.parametrize(('number_range', 'empty'), + ( + ((2, 3), False), + ([2, 3], False), + ([2, 2], False), + ((2, 2), True), + ('[2, 2)', True), + ('(2, 2]', True), + ) + ) + def test_empty(self, number_range, empty): + assert IntInterval(number_range).empty == empty + @mark.parametrize(('number_range', 'degenerate'), ( ((2, 4), False), From 5ef6731af9f7b6a1d9c44bfb3d47c900cd44509e Mon Sep 17 00:00:00 2001 From: Adarsh Krishnan Date: Tue, 4 Feb 2014 12:55:32 +0200 Subject: [PATCH 2/9] Add support for discrete non degenerate empty intervals --- intervals/__init__.py | 9 +++++++-- tests/test_properties.py | 4 +++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/intervals/__init__.py b/intervals/__init__.py index a10995f..2cc88b6 100644 --- a/intervals/__init__.py +++ b/intervals/__init__.py @@ -312,9 +312,14 @@ def degenerate(self): @property def empty(self): + if self.discrete and not self.degenerate: + return ( + self.upper - self.lower == self.step + and not (self.upper_inc or self.lower_inc) + ) return ( - self.upper == self.lower and not - (self.lower_inc and self.upper_inc) + self.upper == self.lower + and not (self.lower_inc and self.upper_inc) ) @property diff --git a/tests/test_properties.py b/tests/test_properties.py index fb97514..a818586 100644 --- a/tests/test_properties.py +++ b/tests/test_properties.py @@ -72,12 +72,14 @@ def test_closed(self, number_range, is_closed): @mark.parametrize(('number_range', 'empty'), ( - ((2, 3), False), + ((2, 3), True), ([2, 3], False), ([2, 2], False), ((2, 2), True), ('[2, 2)', True), ('(2, 2]', True), + ('[2, 3)', False), + ((2, 10), False), ) ) def test_empty(self, number_range, empty): From 8034a521692d9857b0d36e2efced40bb69f5efda Mon Sep 17 00:00:00 2001 From: Adarsh Krishnan Date: Tue, 4 Feb 2014 14:05:47 +0200 Subject: [PATCH 3/9] Refactor test for and operator --- tests/test_operators.py | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/tests/test_operators.py b/tests/test_operators.py index e96c485..161c9ee 100644 --- a/tests/test_operators.py +++ b/tests/test_operators.py @@ -66,17 +66,25 @@ def test_eq_operator(self, interval, interval2): class TestBinaryOperators(object): - @mark.parametrize(('interval1', 'interval2', 'result', 'empty'), ( - ((2, 3), (3, 4), (3, 3), True), - ((2, 3), [3, 4], '[3, 3)', True), - ((2, 5), (3, 10), (3, 5), False), - ('(2, 3]', '[3, 4)', [3, 3], False), - ('(2, 10]', '[3, 40]', [3, 10], False), - ((2, 10), (3, 8), (3, 8), False), + @mark.parametrize(('interval1', 'interval2', 'result'), ( + ((2, 3), (3, 4), (3, 3)), + ((2, 3), [3, 4], '[3, 3)'), + ((2, 5), (3, 10), (3, 5)), + ('(2, 3]', '[3, 4)', [3, 3]), + ('(2, 10]', '[3, 40]', [3, 10]), + ((2, 10), (3, 8), (3, 8)), )) - def test_and_operator(self, interval1, interval2, result, empty): + def test_and_operator(self, interval1, interval2, result): assert ( IntInterval(interval1) & IntInterval(interval2) == IntInterval(result) ) - assert IntInterval(result).empty == empty + + @mark.parametrize(('interval1', 'interval2', 'empty'), ( + ((2, 3), (3, 4), True), + ((2, 3), [3, 4], True), + ([2, 3], (3, 4), True), + ('(2, 3]', '[3, 4)', False), + )) + def test_and_operator_for_empty_results(self, interval1, interval2, empty): + assert (IntInterval(interval1) & IntInterval(interval2)).empty == empty From f2105b140fb2b33f2ad9c7ce272861d336296e11 Mon Sep 17 00:00:00 2001 From: Adarsh Krishnan Date: Sun, 9 Feb 2014 22:43:26 +0200 Subject: [PATCH 4/9] Add tests for initialization with parentheses and square brackets --- tests/test_initialization.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_initialization.py b/tests/test_initialization.py index 6840bb8..f5ce480 100644 --- a/tests/test_initialization.py +++ b/tests/test_initialization.py @@ -88,6 +88,12 @@ def test_supports_integers(self): assert interval.lower_inc assert interval.upper_inc + def test_uses_two_numbers_with_parentheses_as_open_interval(self): + assert IntInterval(1, 2) == IntInterval((1, 2)) + + def test_uses_two_numbers_with_closed_brackets_as_closed_interval(self): + assert IntInterval[1, 2] == IntInterval([1, 2]) + @mark.parametrize('number_range', ( (3, 2), From 9b49e62ab257d2dc043be1e07e29949db4f96c96 Mon Sep 17 00:00:00 2001 From: Adarsh Krishnan Date: Sun, 9 Feb 2014 23:08:51 +0200 Subject: [PATCH 5/9] Add support for parentheses as open intervals --- intervals/__init__.py | 6 ++++++ tests/test_initialization.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/intervals/__init__.py b/intervals/__init__.py index 2cc88b6..42f365c 100644 --- a/intervals/__init__.py +++ b/intervals/__init__.py @@ -151,6 +151,12 @@ def __init__( 30 """ + + if type(bounds) == type(lower_inc) == int and not upper_inc: + bounds = (bounds, lower_inc) + lower_inc = None + upper_inc = None + self.lower, self.upper, self.lower_inc, self.upper_inc = ( self.parser(bounds, lower_inc, upper_inc) ) diff --git a/tests/test_initialization.py b/tests/test_initialization.py index f5ce480..eaac817 100644 --- a/tests/test_initialization.py +++ b/tests/test_initialization.py @@ -91,7 +91,7 @@ def test_supports_integers(self): def test_uses_two_numbers_with_parentheses_as_open_interval(self): assert IntInterval(1, 2) == IntInterval((1, 2)) - def test_uses_two_numbers_with_closed_brackets_as_closed_interval(self): + def test_uses_two_numbers_with_square_brackets_as_closed_interval(self): assert IntInterval[1, 2] == IntInterval([1, 2]) @mark.parametrize('number_range', From 524dfe12575d50486942cd024357d9a88f31198d Mon Sep 17 00:00:00 2001 From: Adarsh Krishnan Date: Sun, 9 Feb 2014 23:08:51 +0200 Subject: [PATCH 6/9] Add support for parentheses as open intervals --- intervals/__init__.py | 6 ++++++ tests/test_initialization.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/intervals/__init__.py b/intervals/__init__.py index 2cc88b6..42f365c 100644 --- a/intervals/__init__.py +++ b/intervals/__init__.py @@ -151,6 +151,12 @@ def __init__( 30 """ + + if type(bounds) == type(lower_inc) == int and not upper_inc: + bounds = (bounds, lower_inc) + lower_inc = None + upper_inc = None + self.lower, self.upper, self.lower_inc, self.upper_inc = ( self.parser(bounds, lower_inc, upper_inc) ) diff --git a/tests/test_initialization.py b/tests/test_initialization.py index f5ce480..eaac817 100644 --- a/tests/test_initialization.py +++ b/tests/test_initialization.py @@ -91,7 +91,7 @@ def test_supports_integers(self): def test_uses_two_numbers_with_parentheses_as_open_interval(self): assert IntInterval(1, 2) == IntInterval((1, 2)) - def test_uses_two_numbers_with_closed_brackets_as_closed_interval(self): + def test_uses_two_numbers_with_square_brackets_as_closed_interval(self): assert IntInterval[1, 2] == IntInterval([1, 2]) @mark.parametrize('number_range', From 35842e4f6d60a33257ed81a30ee20848a5e1e5c4 Mon Sep 17 00:00:00 2001 From: Adarsh Krishnan Date: Sun, 9 Feb 2014 23:17:12 +0200 Subject: [PATCH 7/9] Make corrections to code for support of parentheses --- intervals/__init__.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/intervals/__init__.py b/intervals/__init__.py index 42f365c..b909a43 100644 --- a/intervals/__init__.py +++ b/intervals/__init__.py @@ -151,11 +151,18 @@ def __init__( 30 """ - - if type(bounds) == type(lower_inc) == int and not upper_inc: + # This if-block adds support for parentheses as open intervals. + # Note: If the interval is initialized with the parentheses with two + # objects of same type, eg. + # IntInterval(1, 4) + # the bounds and lower_inc are received of that type and + # upper_inc is None. + # + # eg. + # IntInterval(1, 4) == IntInterval((1, 4)) + if type(bounds) == type(lower_inc) and not upper_inc: bounds = (bounds, lower_inc) - lower_inc = None - upper_inc = None + lower_inc = upper_inc = None self.lower, self.upper, self.lower_inc, self.upper_inc = ( self.parser(bounds, lower_inc, upper_inc) From 08db3aa9f412fc7b54180bc202562d9f10bb6fd0 Mon Sep 17 00:00:00 2001 From: Adarsh Krishnan Date: Sun, 9 Feb 2014 23:39:31 +0200 Subject: [PATCH 8/9] Add support for square brackets as closed intervals --- intervals/__init__.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/intervals/__init__.py b/intervals/__init__.py index b909a43..1b664de 100644 --- a/intervals/__init__.py +++ b/intervals/__init__.py @@ -72,8 +72,24 @@ def wrapper(self, arg): return wrapper +class ClosedInterval(type): + """ + Supports initialization of intervals using square brackets and makes them + closed intervals. + + eg. + + IntInterval[1, 4] == IntInterval([1, 4]) + """ + def __getitem__(self, bounds): + lower_inc = upper_inc = True + return self(bounds, lower_inc, upper_inc) + + @total_ordering class AbstractInterval(object): + __metaclass__ = ClosedInterval + step = None type = None parser = IntervalParser() @@ -151,6 +167,7 @@ def __init__( 30 """ + # This if-block adds support for parentheses as open intervals. # Note: If the interval is initialized with the parentheses with two # objects of same type, eg. From 08e729244b009b1a63c0c709e27f893e12e49261 Mon Sep 17 00:00:00 2001 From: Adarsh Krishnan Date: Sun, 30 Mar 2014 17:15:12 +0300 Subject: [PATCH 9/9] Use six and add support for python3 --- intervals/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/intervals/__init__.py b/intervals/__init__.py index 1b664de..4c949ad 100644 --- a/intervals/__init__.py +++ b/intervals/__init__.py @@ -86,10 +86,9 @@ def __getitem__(self, bounds): return self(bounds, lower_inc, upper_inc) +@six.add_metaclass(ClosedInterval) @total_ordering class AbstractInterval(object): - __metaclass__ = ClosedInterval - step = None type = None parser = IntervalParser()