diff --git a/README.md b/README.md index dae1df22e109aaea41b09ac09db75d9cee013325..47c74b943b4af53f738d84a5cf75a7d084065e78 100644 --- a/README.md +++ b/README.md @@ -137,36 +137,35 @@ isinstance(series, DiscreteSeries) By calling `as_series()` you get a new DiscreteSeries instance returned. -## Ranges +## Intervals -Can be imported from _sai.ranges_. +Can be imported from _sai.intervals_. -Range would have been better called an **interval**. It is a continuous subset -of the real number line. +Interval is a continuous subset of the real number line. -You can create Ranges as follows: +You can create Intervals as follows: ```python -Range(-5, 5, True, False) == Range('<-5;5)') +Interval(-5, 5, True, False) == Interval('<-5;5)') ``` -For more information [use the source](firanka/ranges.py#L33) -Range's are immutable and hashable. They can be sliced: +For more information [use the source](firanka/intervals.py#L33) +Interval's are immutable and hashable. They can be sliced: ```python -Range('<-5;5>')[0:] == Range('<0;5>') +Interval('<-5;5>')[0:] == Interval('<0;5>') ``` Slices work as a both-sides-closed range if both sides are shown! -You can check whether a range contains a point +You can check whether an interval contains a point ```python -5 not in Range('<-1;5)') +5 not in Interval('<-1;5)') ``` Or you can check for strict inclusion ```python -Range('<-1;1>') in Range('<-2;2>') +Interval('<-1;1>') in Interval('<-2;2>') ``` diff --git a/firanka/__init__.py b/firanka/__init__.py index 13b708912bf22a449e7551a97fff687b0e4e19f3..e6d0c4f459e59ecdc1cfced4476692dabc323abc 100644 --- a/firanka/__init__.py +++ b/firanka/__init__.py @@ -1 +1 @@ -__version__ = '0.1.11' +__version__ = '0.1.12' diff --git a/firanka/builders.py b/firanka/builders.py index d01d1d44c6df6e7273ba09c6fb928b2c682a7c5e..028ea68b8d68be841feef014c7373b5b7e10d72c 100644 --- a/firanka/builders.py +++ b/firanka/builders.py @@ -5,7 +5,7 @@ import copy from sortedcontainers import SortedList -from .ranges import Range +from .intervals import Interval from .series import DiscreteSeries """ @@ -34,9 +34,9 @@ class DiscreteSeriesBuilder(object): if index not in self.domain: if index <= self.domain.start: - self.domain = Range(index, self.domain.stop, True, self.domain.right_inc) + self.domain = Interval(index, self.domain.stop, True, self.domain.right_inc) if index >= self.domain.stop: - self.domain = Range(self.domain.start, index, self.domain.left_inc, True) + self.domain = Interval(self.domain.start, index, self.domain.left_inc, True) self.new_data[index] = value diff --git a/firanka/ranges.py b/firanka/intervals.py similarity index 77% rename from firanka/ranges.py rename to firanka/intervals.py index b0554d43099c472a6a52bd85134f7dfca9fbe6ae..1ec6354447d773efd3e13b0712781ebfd08967b8 100644 --- a/firanka/ranges.py +++ b/firanka/intervals.py @@ -6,34 +6,45 @@ import math import six __all__ = [ - 'Range', + 'Interval', 'REAL_SET', 'EMPTY_SET' ] -def _pre_range(fun): # for making sure that first argument gets parsed as a Range +def _pre_range(fun): # for making sure that first argument gets parsed as a Interval @six.wraps(fun) def inner(self, arg, *args, **kwargs): - if not isinstance(arg, Range): - arg = Range(arg) + if not isinstance(arg, Interval): + arg = Interval(arg) return fun(self, arg, *args, **kwargs) return inner -class Range(object): +class Interval(object): """ - Range of real numbers. Immutable. + Interval of real numbers. Immutable. """ __slots__ = ('start', 'stop', 'left_inc', 'right_inc') + @_pre_range + def __add__(self, other): + if self.start > other.start: + return other.__add__(self) + + assert not (self.start > other.start) + + if (self.stop == other.start) and (self.right_inc or other.left_inc): + return Interval(self.start, other.stop, self.right_inc, other.left_inc) + + def translate(self, x): if x == 0: return self else: - return Range(self.start + x, self.stop + x, self.left_inc, - self.right_inc) + return Interval(self.start + x, self.stop + x, self.left_inc, + self.right_inc) def __fromslice(self, rs): start = float('-inf') if rs.start is None else rs.start @@ -55,7 +66,7 @@ class Range(object): def __getargs(self, args): if len(args) == 1: rs, = args - if isinstance(rs, Range): + if isinstance(rs, Interval): args = self.__fromrange(rs) elif isinstance(rs, slice): args = self.__fromslice(rs) @@ -71,10 +82,10 @@ class Range(object): """ Create like: - * Range('<a;b>') - * Range(a, b, is_left_closed_, is_right_closed) - * Range(a, b) - will have both sides closed, unless one is inf - * Range(slice(a, b)) - will have both sides closed, unless one is None + * Interval('<a;b>') + * Interval(a, b, is_left_closed_, is_right_closed) + * Interval(a, b) - will have both sides closed, unless one is inf + * Interval(slice(a, b)) - will have both sides closed, unless one is None :param args: """ @@ -90,12 +101,12 @@ class Range(object): def __contains__(self, x): """ - :type x: index or a Range + :type x: index or a Interval """ if isinstance(x, six.string_types): - x = Range(x) + x = Interval(x) - if isinstance(x, Range): + if isinstance(x, Interval): if ((x.start == self.start) and (x.left_inc ^ self.left_inc)) \ or ((x.stop == self.stop) and (x.right_inc ^ self.right_inc)): return False @@ -118,7 +129,7 @@ class Range(object): return self.stop - self.start def __repr__(self): - return 'Range(%s, %s, %s, %s)' % ( + return 'Interval(%s, %s, %s, %s)' % ( repr(self.start), repr(self.stop), repr(self.left_inc), repr(self.right_inc)) @@ -126,7 +137,7 @@ class Range(object): if not isinstance(item, slice): raise ValueError('must be a slice') - return self.intersection(Range(item)) + return self.intersection(Interval(item)) def __str__(self): return '%s%s;%s%s' % ( @@ -164,7 +175,7 @@ class Range(object): stop = p.stop right_inc = p.right_inc and (stop in q) - return Range(start, stop, left_inc, right_inc) + return Interval(start, stop, left_inc, right_inc) @_pre_range def __eq__(self, other): @@ -177,5 +188,5 @@ class Range(object): return hash(self.start) ^ hash(self.stop) -EMPTY_SET = Range(0, 0, False, False) -REAL_SET = Range(float('-inf'), float('+inf'), False, False) +EMPTY_SET = Interval(0, 0, False, False) +REAL_SET = Interval(float('-inf'), float('+inf'), False, False) diff --git a/firanka/series/base.py b/firanka/series/base.py index 5be4ebe08b758bf818d011bc5645c0b90e2bd568..2f812f2bd98d29b6a455a9a62d3aa5d922052d62 100644 --- a/firanka/series/base.py +++ b/firanka/series/base.py @@ -6,7 +6,7 @@ import inspect from sortedcontainers import SortedList from firanka.exceptions import NotInDomainError -from firanka.ranges import Range, EMPTY_SET +from firanka.intervals import Interval, EMPTY_SET def _has_arguments(fun, n): # used only in assert clauses @@ -23,21 +23,21 @@ class Series(object): """ def __init__(self, domain, comment=u''): - if not isinstance(domain, Range): - domain = Range(domain) + if not isinstance(domain, Interval): + domain = Interval(domain) self.domain = domain self.comment = comment def __getitem__(self, item): """ Return a value for given index, or a subslice of this series - :param item: a float, or a slice, or a Range + :param item: a float, or a slice, or a Interval :return: Series instance or a value :raises NotInDomainError: index not in domain """ - if isinstance(item, (Range, slice)): + if isinstance(item, (Interval, slice)): if isinstance(item, slice): - item = Range(item) + item = Interval(item) if item not in self.domain: raise NotInDomainError('slicing beyond series domain') @@ -80,7 +80,7 @@ class Series(object): points = list(sorted(points)) - domain = domain or Range(points[0], points[-1], True, True) + domain = domain or Interval(points[0], points[-1], True, True) if domain not in self.domain: raise NotInDomainError('points not inside this series!') @@ -120,7 +120,7 @@ class DiscreteSeries(Series): if len(data) == 0: domain = EMPTY_SET elif domain is None: - domain = Range(data[0][0], data[-1][0], True, True) + domain = Interval(data[0][0], data[-1][0], True, True) self.data = data super(DiscreteSeries, self).__init__(domain, *args, **kwargs) diff --git a/firanka/series/modulo.py b/firanka/series/modulo.py index e0063c69e14fd9c1ed50a1d7ea3c017bae6ca4c1..ccd0b4e0bf3e21baee860b93ae633082c0d1ceb7 100644 --- a/firanka/series/modulo.py +++ b/firanka/series/modulo.py @@ -4,7 +4,7 @@ from __future__ import print_function, absolute_import, division import math from .base import Series -from ..ranges import REAL_SET +from ..intervals import REAL_SET class ModuloSeries(Series): diff --git a/tests/test_range.py b/tests/test_range.py index fa286dcaffff2d0596fcb20b5f0772650ab7015e..467d2c996896e2308282e03d8c41bf37740c9222 100644 --- a/tests/test_range.py +++ b/tests/test_range.py @@ -3,31 +3,31 @@ from __future__ import print_function, absolute_import, division import unittest -from firanka.ranges import Range +from firanka.intervals import Interval class TestRange(unittest.TestCase): def do_intersect(self, a, b, val): if type(val) == bool: - p = Range(a).intersection(b) + p = Interval(a).intersection(b) if p.is_empty() != val: self.fail( - '%s ^ %s [=> %s] != %s' % (Range(a), Range(b), p, val)) - p = Range(b).intersection(a) + '%s ^ %s [=> %s] != %s' % (Interval(a), Interval(b), p, val)) + p = Interval(b).intersection(a) if p.is_empty() != val: self.fail( - '%s ^ %s [=> %s] != %s' % (Range(b), Range(a), p, val)) + '%s ^ %s [=> %s] != %s' % (Interval(b), Interval(a), p, val)) else: - self.assertEqual(Range(a).intersection(b), Range(val)) - self.assertEqual(Range(b).intersection(a), Range(val)) + self.assertEqual(Interval(a).intersection(b), Interval(val)) + self.assertEqual(Interval(b).intersection(a), Interval(val)) def test_slicing(self): - self.assertTrue(Range('<-5;5>')[0:] == Range('<0;5>')) + self.assertTrue(Interval('<-5;5>')[0:] == Interval('<0;5>')) def test_isempty(self): - self.assertTrue(Range(-1, -1, False, False).is_empty()) - self.assertFalse(Range(-1, -1, False, True).is_empty()) - self.assertEqual(Range(0, 0, False, False), Range(2, 2, False, False)) + self.assertTrue(Interval(-1, -1, False, False).is_empty()) + self.assertFalse(Interval(-1, -1, False, True).is_empty()) + self.assertEqual(Interval(0, 0, False, False), Interval(2, 2, False, False)) def test_intersection(self): self.do_intersect('<-10;1>', '<2;3>', True) @@ -38,31 +38,31 @@ class TestRange(unittest.TestCase): self.do_intersect('<-5;5>', '(-5;5)', '(-5;5)') def test_str_and_repr_and_bool(self): - p = Range(-1, 1, True, True) + p = Interval(-1, 1, True, True) self.assertEqual(eval(repr(p)), p) - self.assertEqual(str(Range(-1, 1)), '<-1;1>') + self.assertEqual(str(Interval(-1, 1)), '<-1;1>') def test_constructor(self): - self.assertRaises(ValueError, lambda: Range('#2;3>')) - self.assertRaises(ValueError, lambda: Range('(2;3!')) - self.assertRaises(ValueError, lambda: Range('<-inf;3)')) - self.assertEqual(Range(1, 2, True, False), Range('<1;2)')) - self.assertEqual(Range(1, 2, True, False), '<1;2)') + self.assertRaises(ValueError, lambda: Interval('#2;3>')) + self.assertRaises(ValueError, lambda: Interval('(2;3!')) + self.assertRaises(ValueError, lambda: Interval('<-inf;3)')) + self.assertEqual(Interval(1, 2, True, False), Interval('<1;2)')) + self.assertEqual(Interval(1, 2, True, False), '<1;2)') - r = Range(1, 2, True, False) - self.assertEqual(r, Range(r)) + r = Interval(1, 2, True, False) + self.assertEqual(r, Interval(r)) def test_contains(self): - self.assertFalse(-1 in Range('<-10;-1)')) - self.assertTrue(-10 in Range('<-10;-1)')) - self.assertFalse(-10 in Range('(-10;-1>')) - self.assertTrue(-1 in Range('(-10;-1>')) - self.assertTrue(-5 in Range('(-10;-1>')) - self.assertFalse(-20 in Range('(-10;-1>')) - self.assertFalse(1 in Range('(-10;-1>')) - self.assertFalse('<-10;-1>' in Range('(-10;-1)')) - self.assertFalse('<-10;-1)' in Range('(-10;-1>')) + self.assertFalse(-1 in Interval('<-10;-1)')) + self.assertTrue(-10 in Interval('<-10;-1)')) + self.assertFalse(-10 in Interval('(-10;-1>')) + self.assertTrue(-1 in Interval('(-10;-1>')) + self.assertTrue(-5 in Interval('(-10;-1>')) + self.assertFalse(-20 in Interval('(-10;-1>')) + self.assertFalse(1 in Interval('(-10;-1>')) + self.assertFalse('<-10;-1>' in Interval('(-10;-1)')) + self.assertFalse('<-10;-1)' in Interval('(-10;-1>')) - self.assertTrue(Range('<-5;5>') in Range('<-10;10>')) - self.assertTrue('(-1;6)' in Range(-10.0, 10.0, True, False)) - self.assertTrue('<0.5;1.5>' in Range('<0;2>')) + self.assertTrue(Interval('<-5;5>') in Interval('<-10;10>')) + self.assertTrue('(-1;6)' in Interval(-10.0, 10.0, True, False)) + self.assertTrue('<0.5;1.5>' in Interval('<0;2>')) diff --git a/tests/test_series.py b/tests/test_series.py index d35862a8a5ff107a0e740a6cd348fc50807613b7..0329e8c206ed6dd52facaa57116305238369810a 100644 --- a/tests/test_series.py +++ b/tests/test_series.py @@ -5,7 +5,7 @@ import math import unittest from firanka.exceptions import NotInDomainError -from firanka.ranges import Range +from firanka.intervals import Interval from firanka.series import DiscreteSeries, FunctionSeries, ModuloSeries, \ LinearInterpolationSeries, Series @@ -42,7 +42,7 @@ class TestDiscreteSeries(unittest.TestCase): self.assertRaises(NotInDomainError, lambda: s[2.5]) s = DiscreteSeries([[0, 0], [1, 1], [2, 2]], - domain=Range(0, 3, True, True)) + domain=Interval(0, 3, True, True)) self.assertEqual(s[0], 0) self.assertEqual(s[0.5], 0) self.assertEqual(s[1], 1) @@ -120,7 +120,7 @@ class TestDiscreteSeries(unittest.TestCase): EPTS = [x * x ** 2 for x in PTS] self.assertEqual(sc.eval_points(PTS), EPTS) - self.assertTrue(Range('<0;2)') in sc.domain) + self.assertTrue(Interval('<0;2)') in sc.domain) def test_discretize(self): # note the invalid data for covering this domain