ScoDoc/app/scodoc/intervals.py

249 lines
7.4 KiB
Python
Raw Normal View History

2020-09-26 16:19:37 +02:00
# -*- mode: python -*-
# -*- coding: utf-8 -*-
# Code from http://code.activestate.com/recipes/457411/
from bisect import bisect_left, bisect_right
2021-07-09 13:59:01 +02:00
2020-09-26 16:19:37 +02:00
class intervalmap(object):
"""
2021-01-01 18:40:47 +01:00
This class maps a set of intervals to a set of values.
>>> i = intervalmap()
>>> i[0:5] = '0-5'
>>> i[8:12] = '8-12'
>>> print i[2]
0-5
>>> print i[10]
8-12
>>> print repr(i[-1])
None
>>> print repr(i[17])
None
>>> i[4:9] = '4-9'
>>> print [(j,i[j]) for j in range(6)]
[(0, '0-5'), (1, '0-5'), (2, '0-5'), (3, '0-5'), (4, '4-9'), (5, '4-9')]
>>> print list(i.items())
[((0, 4), '0-5'), ((4, 9), '4-9'), ((9, 12), '8-12')]
>>> i[:0] = 'less than 0'
>>> i[-5]
'less than 0'
>>> i[0]
'0-5'
>>> print list(i.items())
[((None, 0), 'less than 0'), ((0, 4), '0-5'), ((4, 9), '4-9'), ((9, 12), '8-12')]
>>> i[21:] = 'more than twenty'
>>> i[42]
'more than twenty'
>>> i[10.5:15.5] = '10.5-15.5'
>>> i[11.5]
'10.5-15.5'
>>> i[0.5]
'0-5'
>>> print list(i.items())
[((None, 0),... ((9, 10.5), '8-12'), ((10.5, 15.5), '10.5-15.5'), ((21, None),...
>>> i = intervalmap()
>>> i[0:2] = 1
>>> i[2:8] = 2
>>> i[4:] = 3
>>> i[5:6] = 4
>>> i
{[0, 2] => 1, [2, 4] => 2, [4, 5] => 3, [5, 6] => 4, [6, None] => 3}
2020-09-26 16:19:37 +02:00
"""
def __init__(self):
"""
2021-01-01 18:40:47 +01:00
Initializes an empty intervalmap.
2020-09-26 16:19:37 +02:00
"""
self._bounds = []
self._items = []
self._upperitem = None
def __setitem__(self, _slice, _value):
"""
2021-01-01 18:40:47 +01:00
Sets an interval mapping.
2020-09-26 16:19:37 +02:00
"""
assert isinstance(_slice, slice), "The key must be a slice object"
if _slice.start is None:
start_point = -1
else:
start_point = bisect_left(self._bounds, _slice.start)
if _slice.stop is None:
end_point = -1
else:
end_point = bisect_left(self._bounds, _slice.stop)
if start_point >= 0:
if (
start_point < len(self._bounds)
and self._bounds[start_point] < _slice.start
):
start_point += 1
if end_point >= 0:
self._bounds[start_point:end_point] = [_slice.start, _slice.stop]
if start_point < len(self._items):
self._items[start_point:end_point] = [
self._items[start_point],
_value,
]
else:
self._items[start_point:end_point] = [self._upperitem, _value]
else:
self._bounds[start_point:] = [_slice.start]
if start_point < len(self._items):
self._items[start_point:] = [self._items[start_point], _value]
else:
self._items[start_point:] = [self._upperitem]
self._upperitem = _value
else:
if end_point >= 0:
self._bounds[:end_point] = [_slice.stop]
self._items[:end_point] = [_value]
else:
self._bounds[:] = []
self._items[:] = []
self._upperitem = _value
def __getitem__(self, _point):
"""
2021-01-01 18:40:47 +01:00
Gets a value from the mapping.
2020-09-26 16:19:37 +02:00
"""
assert not isinstance(_point, slice), "The key cannot be a slice object"
index = bisect_right(self._bounds, _point)
if index < len(self._bounds):
return self._items[index]
else:
return self._upperitem
def items(self):
"""
2021-01-01 18:40:47 +01:00
Returns an iterator with each item being
((low_bound,high_bound), value). The items are returned
in order.
2020-09-26 16:19:37 +02:00
"""
previous_bound = None
2021-07-09 13:59:01 +02:00
for b, v in zip(self._bounds, self._items):
2020-09-26 16:19:37 +02:00
if v is not None:
yield (previous_bound, b), v
previous_bound = b
if self._upperitem is not None:
yield (previous_bound, None), self._upperitem
def values(self):
"""
2021-01-01 18:40:47 +01:00
Returns an iterator with each item being a stored value. The items
are returned in order.
2020-09-26 16:19:37 +02:00
"""
for v in self._items:
if v is not None:
yield v
if self._upperitem is not None:
yield self._upperitem
def __repr__(self):
s = []
for b, v in self.items():
if v is not None:
s.append("[%r, %r] => %r" % (b[0], b[1], v))
return "{" + ", ".join(s) + "}"
if __name__ == "__main__":
# Test 1
i = intervalmap()
i[9:] = "!"
assert repr(i) == "{[9, None] => '!'}"
i[:5] = "Hello"
i[6:7] = "World"
assert repr(i) == "{[None, 5] => 'Hello', [6, 7] => 'World', [9, None] => '!'}"
i[8:10] = "(Test)"
assert (
repr(i)
== "{[None, 5] => 'Hello', [6, 7] => 'World', [8, 10] => '(Test)', [10, None] => '!'}"
)
i[:3] = "My,"
assert (
repr(i)
== "{[None, 3] => 'My,', [3, 5] => 'Hello', [6, 7] => 'World', [8, 10] => '(Test)', [10, None] => '!'}"
)
2021-02-03 22:00:41 +01:00
i[5.5:6] = "Cruel" # pylint: disable=invalid-slice-index
2020-09-26 16:19:37 +02:00
assert (
repr(i)
== "{[None, 3] => 'My,', [3, 5] => 'Hello', [5.5, 6] => 'Cruel', [6, 7] => 'World', [8, 10] => '(Test)', [10, None] => '!'}"
)
2021-02-03 22:00:41 +01:00
i[6:6.5] = "And Harsh" # pylint: disable=invalid-slice-index
2020-09-26 16:19:37 +02:00
assert (
repr(i)
== "{[None, 3] => 'My,', [3, 5] => 'Hello', [5.5, 6] => 'Cruel', [6, 6.5] => 'And Harsh', [6.5, 7] => 'World', [8, 10] => '(Test)', [10, None] => '!'}"
)
2021-02-03 22:00:41 +01:00
i[5.9:6.6] = None # pylint: disable=invalid-slice-index
2020-09-26 16:19:37 +02:00
assert (
repr(i)
== "{[None, 3] => 'My,', [3, 5] => 'Hello', [5.5, 5.9000000000000004] => 'Cruel', [6.5999999999999996, 7] => 'World', [8, 10] => '(Test)', [10, None] => '!'}"
)
2021-07-09 13:59:01 +02:00
assert " ".join(list(i.values())) == "My, Hello Cruel World (Test) !"
2020-09-26 16:19:37 +02:00
print("Test 1 OK")
# Test 2
i = intervalmap()
i[:0] = "A"
i[2:5] = "B"
i[8:10] = "C"
i[12:] = "D"
assert (
repr(i)
== "{[None, 0] => 'A', [2, 5] => 'B', [8, 10] => 'C', [12, None] => 'D'}"
)
i[:] = "K"
assert repr(i) == "{[None, None] => 'K'}"
assert i[5] == "K"
i[0:10] = "L"
i[6:8] = "M"
i[20:] = "J"
assert i[-1] == "K"
assert i[5] == "L"
assert i[7] == "M"
assert i[9] == "L"
assert i[15] == "K"
assert i[42] == "J"
print("Test 2 OK")
# Test 3
try:
from datetime import datetime
except:
print("Test 3 skipped")
else:
i = intervalmap()
2021-02-03 22:00:41 +01:00
i[: datetime(2005, 10, 24)] = "A" # pylint: disable=invalid-slice-index
i[
datetime(2005, 11, 11) : datetime( # pylint: disable=invalid-slice-index
2005, 11, 17
)
] = "B"
i[datetime(2005, 11, 30) :] = "C" # pylint: disable=invalid-slice-index
2020-09-26 16:19:37 +02:00
assert i[datetime(2005, 9, 25)] == "A"
assert i[datetime(2005, 10, 23)] == "A"
assert i[datetime(2005, 10, 26)] == None
assert i[datetime(2005, 11, 9)] == None
assert i[datetime(2005, 11, 16)] == "B"
assert i[datetime(2005, 11, 23)] == None
assert i[datetime(2005, 11, 29)] == None
assert i[datetime(2005, 11, 30)] == "C"
assert i[datetime(2005, 12, 3)] == "C"
print("Test 3 OK")
try:
import doctest
except:
print("Skipping the doctests")
else:
print("And now, the doctests")
doctest.testmod(optionflags=doctest.ELLIPSIS)