Skip to content

Commit

Permalink
Fixed iteration of rangeset returning an empty range when rangeset is…
Browse files Browse the repository at this point in the history
… empty, bug #4. Added documentation for rangeset.__iter__. Bumped version number to 0.3.0 since this fix is backwards incompatible.
  • Loading branch information
runfalk committed Jul 15, 2016
1 parent b42e26f commit c861aa0
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 28 deletions.
2 changes: 1 addition & 1 deletion doc/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ Range set
~~~~~~~~~
.. autoclass:: spans.settypes.rangeset
:members:
:special-members: __len__
:special-members: __iter__, __len__

Discrete range set
~~~~~~~~~~~~~~~~~~
Expand Down
10 changes: 7 additions & 3 deletions doc/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@ Version are structured like the following: ``<major>.<minor>.<bugfix>``. The
first `0.1` release does not properly adhere to this. Unless explicitly stated,
changes are made by `Andreas Runfalk <https://github.com/runfalk>`_.

Version 0.2.2
Version 0.3.0
-------------
Unreleased

- Fixed `bug #3 <https://github.com/runfalk/spans/issues/3>`_, intersection of
multiple range sets not working correctly.
- Added documentation for :meth:`~spans.settypes.rangeset.__iter__`
- Fixed intersection of multiple range sets not working correctly
(`bug #3 <https://github.com/runfalk/spans/issues/3>`_)
- Fixed iteration of :class:`~spans.settypes.rangeset` returning an empty range
when ``rangeset`` is empty
(`bug #4 <https://github.com/runfalk/spans/issues/4>`_)

Version 0.2.1
-------------
Expand Down
2 changes: 1 addition & 1 deletion spans/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"""

__version__ = "0.2.1"
__version__ = "0.3.0"
__all__ = [
"intrange",
"floatrange",
Expand Down
58 changes: 35 additions & 23 deletions spans/settypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ class rangeset(object):
number of distinct ranges required to represent this set. See
:meth:`~spans.settypes.rangeset.__len__`.
- All range sets are iterable. The iterator returns a range for each
iteration.
iteration. See :meth:`~spans.settypes.rangeset.__iter__` for more details.
- All range sets are invertible using the ``~`` operator. The result is a
new range set that does not intersect the original range set at all.
Expand All @@ -121,18 +121,15 @@ class rangeset(object):
__slots__ = ("_list",)

def __init__(self, ranges):
self._list = [self.type.empty()]
self._list = []

for r in ranges:
self.add(r)

def __repr__(self):
if not self:
return "{0.__class__.__name__}([])".format(self)
else:
return "{instance.__class__.__name__}({list!r})".format(
instance=self,
list=self._list)
return "{instance.__class__.__name__}({list!r})".format(
instance=self,
list=self._list)

# Support pickling using the default ancient pickling protocol for Python 2.7
def __getstate__(self):
Expand All @@ -152,9 +149,27 @@ def __nonzero__(self):
True
"""
return bool(len(self._list) != 1 or self._list[0])
return bool(self._list)

def __iter__(self):
"""
Returns an iterator over all ranges within this set. Note that this
iterates over the normalized version of the range set:
>>> list(intrangeset(
... [intrange(1, 5), intrange(5, 10), intrange(15, 20)]))
[intrange([1,10)), intrange([15,20))]
If the set is empty an empty iterator is returned.
>>> list(intrangeset([]))
[]
.. versionchanged:: 0.3.0
This method used to return an empty range when the rangeset was
empty.
"""

return iter(self._list)

def __eq__(self, other):
Expand Down Expand Up @@ -182,7 +197,7 @@ def __len__(self):
.. versionadded:: 0.2.0
"""
return len(self._list) if self else 0
return len(self._list)

def __invert__(self):
"""
Expand Down Expand Up @@ -252,6 +267,12 @@ def contains(self, item):
.. versionadded:: 0.2.0
"""

# All range sets contain the empty range. However, we must verify the
# type of what is being passed as well to make sure we indeed got an
# empty set of the correct type.
if isinstance(item, self.type) and not item:
return True

for r in self._list:
if r.contains(item) is True:
return True
Expand Down Expand Up @@ -284,12 +305,6 @@ def add(self, item):
if not item:
return

# If the list currently only have an empty range in it replace it by this
# item
if not self:
self._list = [item]
return

i = 0
buffer = []
while i < len(self._list):
Expand Down Expand Up @@ -333,7 +348,8 @@ def remove(self, item):

self._test_type(item)

# If the list currently only have an empty range do nothing
# If the list currently only have an empty range do nothing since an
# empty rangeset can't be removed from anyway.
if not self:
return

Expand Down Expand Up @@ -363,10 +379,6 @@ def remove(self, item):
break
i += 1

# If the list was wiped clean we must at least have the empty range in it
if not self._list:
self._list.append(self.type.empty())

def span(self):
"""
Return a range that spans from the first point to the last point in this
Expand All @@ -379,9 +391,9 @@ def span(self):
:return: A new range the contains this entire range set.
"""

# If the list is empty we treat it specially by returning an empty range
# If the set is empty we treat it specially by returning an empty range
if not self:
return self._list[0]
return self.type.empty()

return self._list[0].replace(
upper=self._list[-1].upper,
Expand Down
21 changes: 21 additions & 0 deletions spans/tests/test_settypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,20 @@ def test_copy(self):
self.assertEqual(list(rset), list(rcopy))
self.assertIsNot(rset._list, rcopy._list)

def test_contains(self):
rset = intrangeset([intrange(1, 10)])

self.assertTrue(rset.contains(intrange(1, 5)))
self.assertTrue(rset.contains(intrange(5, 10)))
self.assertFalse(rset.contains(intrange(5, 15)))

self.assertTrue(rset.contains(1))
self.assertTrue(rset.contains(5))
self.assertFalse(rset.contains(10))

self.assertTrue(rset.contains(intrange.empty()))
self.assertTrue(intrangeset([]).contains(intrange.empty()))

def test_add(self):
rset = intrangeset([intrange(1, 15)])
rset.add(intrange(5, 15))
Expand Down Expand Up @@ -168,3 +182,10 @@ def test_bug3_intersection(self):

self.assertEqual(
rangeset_a.intersection(rangeset_b, rangeset_c), rangeset_empty)

def test_bug4_empty_set_iteration(self):
"""
`Bug #4 <https://github.com/runfalk/spans/issues/4>`
"""

self.assertEqual(list(intrangeset([])), [])

0 comments on commit c861aa0

Please sign in to comment.