From f67edb7c53ab9e98ca513478c37a2d73937bfab8 Mon Sep 17 00:00:00 2001
From: Piotr Maslanka <piotr.maslanka@henrietta.com.pl>
Date: Fri, 8 Dec 2017 20:12:31 +0100
Subject: [PATCH] reqs

---
 firanka/series/__init__.py       |  43 +++++++++++-
 firanka/series/exceptions.py     |  18 +++++
 firanka/series/range.py          | 116 +++++++++++++++++++++++++++++++
 requirements.txt                 |   1 +
 setup.py                         |   2 +-
 tests/test_series/test_range.py  |  16 +++++
 tests/test_series/test_series.py |  15 ----
 7 files changed, 192 insertions(+), 19 deletions(-)
 create mode 100644 firanka/series/exceptions.py
 create mode 100644 firanka/series/range.py
 create mode 100644 tests/test_series/test_range.py
 delete mode 100644 tests/test_series/test_series.py

diff --git a/firanka/series/__init__.py b/firanka/series/__init__.py
index af701b5..2d68821 100644
--- a/firanka/series/__init__.py
+++ b/firanka/series/__init__.py
@@ -5,14 +5,39 @@ import logging
 
 logger = logging.getLogger(__name__)
 
+from .exceptions import OutOfRangeError, EmptyDomainError
+
+
 
 class DataSeries(object):
     """
     Finite mapping from x: REAL => object
     """
 
-    def __init__(self, data=None):
-        self.data = data or []
+    def __init__(self, data, domain_end=None):
+        self.data = data
+
+        if domain_end is None:
+            try:
+                self.domain_end = data[-1][0]
+            except IndexError:
+                self.domain_end = None
+        else:
+            self.domain_end = domain_end
+
+    @property
+    def domain(self):
+        try:
+            start = self.data[0][0]
+            stop = self.domain_end
+            assert start <= stop
+            return start, stop
+        except IndexError:
+            return EmptyDomainError
+
+    def __contains__(self, index):
+        start, stop = self.domain
+        return start <= index <= stop
 
     def length(self):
         """
@@ -20,6 +45,18 @@ class DataSeries(object):
         :return: float
         """
         try:
-            return self.data[-1] - self.data[0]
+            start, stop = self.domain
+
+            return stop-start
         except IndexError:
             return 0.0
+        except TypeError:
+            return 0.0 # domain_end is None
+
+    def __getitem__(self, index):
+        if index not in self:
+            raise OutOfRangeError('index not within domain', index)
+
+        for k, v in self.data:
+            if k <= index:
+                return v
diff --git a/firanka/series/exceptions.py b/firanka/series/exceptions.py
new file mode 100644
index 0000000..fd588a8
--- /dev/null
+++ b/firanka/series/exceptions.py
@@ -0,0 +1,18 @@
+# coding=UTF-8
+from __future__ import print_function, absolute_import, division
+import six
+import logging
+
+logger = logging.getLogger(__name__)
+
+
+class FirankaException(Exception):
+    pass
+
+
+class OutOfRangeError(FirankaException):
+    pass
+
+
+class EmptyDomainError(FirankaException):
+    pass
\ No newline at end of file
diff --git a/firanka/series/range.py b/firanka/series/range.py
new file mode 100644
index 0000000..bf60693
--- /dev/null
+++ b/firanka/series/range.py
@@ -0,0 +1,116 @@
+# coding=UTF-8
+from __future__ import print_function, absolute_import, division
+import six
+import logging
+import re
+from satella.coding import for_argument
+
+logger = logging.getLogger(__name__)
+
+
+class Range(object):
+    """
+    Range of real numbers
+    """
+    def __init__(self, *args):
+        if len(args) == 1:
+            rs, = args
+            assert rs.startswith('<') or rs.startswith('(')
+            assert rs.endswith('>') or rs.endswith(')')
+
+            lend_inclusive = rs[0] == '<'
+            rend_inclusive = rs[-1] == '>'
+
+            rs = rs[1:-1]
+            start, stop = map(float, rs.split(';'))
+        elif isinstance(args[0], Range):
+            start = args[0].range
+            stop = args[0].stop
+            lend_inclusive = args[0].lend_inclusive
+            rend_inclusive = args[0].rend_inclusive
+        else:
+            start, stop, lend_inclusive, rend_inclusive = args
+
+        self.start = start
+        self.stop = stop
+        self.lend_inclusive = lend_inclusive
+        self.rend_inclusive = rend_inclusive
+
+    def __contains__(self, x):
+        if x == self.start:
+            return self.lend_inclusive
+
+        if x == self.stop:
+            return self.rend_inclusive
+
+        return self.start < x < self.stop
+
+    def is_empty(self):
+        return (self.start == self.stop) and (not self.lend_inclusive) and (
+        not self.rend_inclusive)
+
+    def __len__(self):
+        return self.stop - self.start
+
+    def __repr__(self):
+        return str(self)
+        return 'Range(%s, %s, %s, %s)' % (repr(self.start), repr(self.stop), repr(self.lend_inclusive), repr(self.rend_inclusive))
+
+    def __bool__(self):
+        """True if not empty"""
+        return not self.is_empty()
+
+    def __str__(self):
+        return '%s%s;%s%s' % (
+            '<' if self.lend_inclusive else '(',
+            self.start,
+            self.stop,
+            '>' if self.rend_inclusive else ')',
+        )
+
+    def intersection(self, y):
+        if not isinstance(y, Range): y = Range(y)
+
+        x = self
+
+        # Check for intersection being impossible
+        if (x.stop < y.start) or (x.start > y.stop) or \
+            (x.stop == y.start and not x.rend_inclusive and not y.lend_inclusive) or \
+            (x.start == x.stop and not x.lend_inclusive and not y.rend_inclusive):
+            return EMPTY_RANGE
+
+        # Check for range extension
+        if (x.start == y.stop) and (x.lend_inclusive or y.lend_inclusive):
+            return Range(y.start, x.stop, y.lend_inclusive, x.rend_inclusive)
+
+        if (x.start == y.stop) and (x.lend_inclusive or y.lend_inclusive):
+            return Range(y.start, x.stop, y.lend_inclusive, x.rend_inclusive)
+
+
+        if x.start == y.start:
+            start = x.start
+            lend_inclusive = x.lend_inclusive or y.lend_inclusive
+        else:
+            p = x if x.start > y.start else y
+            start = p.start
+            lend_inclusive = p.lend_inclusive
+
+        if x.stop == y.stop:
+            stop = x.stop
+            rend_inclusive = x.rend_inclusive or y.rend_inclusive
+        else:
+            p = x if x.stop < y.stop else y
+            stop = p.stop
+            rend_inclusive = p.rend_inclusive
+
+        return Range(start, stop, lend_inclusive, rend_inclusive)
+
+    def __eq__(self, other):
+        if not isinstance(other, Range): other = Range(other)
+        return self.start == other.start and self.stop == other.stop and self.lend_inclusive == other.lend_inclusive and self.rend_inclusive == other.rend_inclusive
+
+    def __hash__(self):
+        return hash(self.start) ^ hash(self.stop)
+
+
+EMPTY_RANGE = Range(0, 0, False, False)
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index ffe2fce..51695ee 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1 +1,2 @@
 six
+satella
diff --git a/setup.py b/setup.py
index 7861f14..ca77cde 100644
--- a/setup.py
+++ b/setup.py
@@ -7,6 +7,6 @@ setup(
     version=__version__,
     packages=find_packages(exclude=['tests.*', 'tests']),
     tests_require=["nose", 'coverage>=4.0,<4.4'],
-    install_requires=['six'],
+    install_requires=open('requirements.txt', 'r').readlines(),
     test_suite='nose.collector',
 )
diff --git a/tests/test_series/test_range.py b/tests/test_series/test_range.py
new file mode 100644
index 0000000..7c54260
--- /dev/null
+++ b/tests/test_series/test_range.py
@@ -0,0 +1,16 @@
+# coding=UTF-8
+from __future__ import print_function, absolute_import, division
+import six
+import unittest
+from firanka.series.range import Range
+
+class TestRange(unittest.TestCase):
+    def test_intersection(self):
+
+        self.assertFalse(Range(-10, -1, True, True).intersection('<2;3>'))
+        self.assertFalse(Range(-10, -1, True, False).intersection('(-1;3>'))
+        self.assertEquals(Range('<-10;-1)').intersection('<-1;1>'), '<-1;1>')
+
+
+    def test_str(self):
+        self.assertEqual(str(Range(-1, 1, True, True)), '<-1;1>')
\ No newline at end of file
diff --git a/tests/test_series/test_series.py b/tests/test_series/test_series.py
deleted file mode 100644
index 833ce84..0000000
--- a/tests/test_series/test_series.py
+++ /dev/null
@@ -1,15 +0,0 @@
-# coding=UTF-8
-from __future__ import print_function, absolute_import, division
-import six
-import unittest
-from firanka.series import DataSeries
-
-
-class TestSeries(unittest.TestCase):
-    def test_ds(self):
-
-        ds = DataSeries()
-        self.assertAlmostEqual(ds.length(), 0.0)
-
-        ds = DataSeries([[0,1], [10,2]])
-        self.assertAlmostEqual(ds.length(), 10.0)
-- 
GitLab