163 lines
4.6 KiB
Python
163 lines
4.6 KiB
Python
# -*- coding: utf-8 -*-
|
||
try:
|
||
from functools import total_ordering
|
||
except ImportError:
|
||
from total_ordering import total_ordering
|
||
|
||
import six
|
||
|
||
|
||
class NumberRangeException(Exception):
|
||
pass
|
||
|
||
|
||
class RangeBoundsException(NumberRangeException):
|
||
def __init__(self, min_value, max_value):
|
||
self.message = 'Min value %d is bigger than max value %d.' % (
|
||
min_value,
|
||
max_value
|
||
)
|
||
|
||
|
||
@total_ordering
|
||
class NumberRange(object):
|
||
def __init__(self, *args):
|
||
if len(args) > 2:
|
||
raise NumberRangeException(
|
||
'NumberRange takes at most two arguments'
|
||
)
|
||
elif len(args) == 2:
|
||
lower, upper = args
|
||
if lower > upper:
|
||
raise RangeBoundsException(lower, upper)
|
||
self.lower = lower
|
||
self.upper = upper
|
||
self.lower_inc = self.upper_inc = True
|
||
else:
|
||
if isinstance(args[0], six.integer_types):
|
||
self.lower = self.upper = args[0]
|
||
self.lower_inc = self.upper_inc = True
|
||
elif isinstance(args[0], six.string_types):
|
||
if ',' not in args[0]:
|
||
self.lower, self.upper = self.parse_range(args[0])
|
||
self.lower_inc = self.upper_inc = True
|
||
else:
|
||
self.from_range_with_bounds(args[0])
|
||
elif hasattr(args[0], 'lower') and hasattr(args[0], 'upper'):
|
||
self.lower = args[0].lower
|
||
self.upper = args[0].upper
|
||
if not args[0].lower_inc:
|
||
self.lower += 1
|
||
|
||
if not args[0].upper_inc:
|
||
self.upper -= 1
|
||
|
||
def from_range_with_bounds(self, value):
|
||
"""
|
||
Returns new NumberRange object from normalized number range format.
|
||
|
||
Example ::
|
||
|
||
range = NumberRange.from_normalized_str('[23, 45]')
|
||
range.lower = 23
|
||
range.upper = 45
|
||
|
||
range = NumberRange.from_normalized_str('(23, 45]')
|
||
range.lower = 24
|
||
range.upper = 45
|
||
|
||
range = NumberRange.from_normalized_str('(23, 45)')
|
||
range.lower = 24
|
||
range.upper = 44
|
||
"""
|
||
values = value[1:-1].split(',')
|
||
try:
|
||
lower, upper = map(
|
||
lambda a: int(a.strip()), values
|
||
)
|
||
except ValueError as e:
|
||
raise NumberRangeException(e.message)
|
||
|
||
self.lower_inc = value[0] == '('
|
||
if self.lower_inc:
|
||
lower += 1
|
||
|
||
self.upper_inc = value[-1] == ')'
|
||
if self.upper_inc:
|
||
upper -= 1
|
||
|
||
self.lower = lower
|
||
self.upper = upper
|
||
|
||
def parse_range(self, value):
|
||
if value is not None:
|
||
values = value.split('-')
|
||
if len(values) == 1:
|
||
lower = upper = int(value.strip())
|
||
else:
|
||
try:
|
||
lower, upper = map(
|
||
lambda a: int(a.strip()), values
|
||
)
|
||
except ValueError as e:
|
||
raise NumberRangeException(str(e))
|
||
return lower, upper
|
||
|
||
@property
|
||
def normalized(self):
|
||
return '[%s, %s]' % (self.lower, self.upper)
|
||
|
||
def __eq__(self, other):
|
||
try:
|
||
return (
|
||
self.lower == other.lower and
|
||
self.upper == other.upper
|
||
)
|
||
except AttributeError:
|
||
return NotImplemented
|
||
|
||
def __ne__(self, other):
|
||
return not (self == other)
|
||
|
||
def __gt__(self, other):
|
||
try:
|
||
return self.lower > other.lower and self.upper > other.upper
|
||
except AttributeError:
|
||
return NotImplemented
|
||
|
||
def __repr__(self):
|
||
return 'NumberRange(%r, %r)' % (self.lower, self.upper)
|
||
|
||
def __str__(self):
|
||
if self.lower != self.upper:
|
||
return '%s - %s' % (self.lower, self.upper)
|
||
return str(self.lower)
|
||
|
||
def __add__(self, other):
|
||
"""
|
||
[a, b] + [c, d] = [a + c, b + d]
|
||
"""
|
||
try:
|
||
return NumberRange(
|
||
self.lower + other.lower,
|
||
self.upper + other.upper
|
||
)
|
||
except AttributeError:
|
||
return NotImplemented
|
||
|
||
def __sub__(self, other):
|
||
"""
|
||
Defines the substraction operator.
|
||
|
||
As defined in wikipedia:
|
||
|
||
[a, b] − [c, d] = [a − d, b − c]
|
||
"""
|
||
try:
|
||
return NumberRange(
|
||
self.lower - other.upper,
|
||
self.upper - other.lower
|
||
)
|
||
except AttributeError:
|
||
return NotImplemented
|