Source code for ytfs.range_t

"""
Module that provides range_t class which offers a simple and compact way of representing number sets.
"""

from copy import deepcopy

[docs]class range_t(): """ Class offers simple representation of multiple ranges as one object. Subranges are represented as two element tuples, where first element is a left boundary and second element is a right range boundary. The left one is always included, whereas right is always excluded from the range. Attributes ---------- __has : set Set of subranges. Parameters ---------- initset : set, optional Initial set of subranges. Empty set is the default value. """ def __init__(self, initset=set()): self.__has = set() if not isinstance(initset, set): raise TypeError("Expected set of tuples") for t in initset: if not isinstance(t, tuple) or len(t) != 2 or t[1] <= t[0] or t[1] < 0: raise ValueError("Your tuples are wrong :(") self.__has = initset self.__optimize() def __match_l(self, k, _set): """ Method for searching subranges from `_set` that overlap on `k` range. Parameters ---------- k : tuple or list or range Range for which we search overlapping subranges from `_set`. _set : set Subranges set. Returns ------- matched : set Set of subranges from `_set` that overlaps on `k`. """ return {r for r in _set if k[0] in range(*r) or k[1] in range(*r) or (k[0] < r[0] and k[1] >= r[1])} #k partially or wholly in r #r is wholly contained by k def __optimize(self): """ Merge overlapping or contacting subranges from ``self.__has`` attribute and update it. Called from all methods that modify object contents. Returns ------- None Method does not return. It does internal modifications on ``self.__has`` attribute. """ ret = [] for (begin, end) in sorted(self.__has): if ret and begin <= ret[-1][1] < end: # when current range overlaps with the last one from ret ret[-1] = (ret[-1][0], end) elif not ret or begin > ret[-1][1]: ret.append( (begin, end) ) self.__has = set(ret) def __val_convert(self, val): # maybe I should make a decorator from this. """ Convert input data to a range tuple (start, end). Parameters ---------- val : int or tuple or list or range Two element indexed object, that represents a range, or integer. Returns ------- converted : tuple Tuple that represents a range. """ converted = (0, 0) # just in case # validate and change val to a tuple. if isinstance(val, range) and 0 <= val.start < val.stop and val.step == 1: converted = (val.start, val.stop) elif (isinstance(val, tuple) or isinstance(val, list)) and 0 <= val[0] < val[1] and len(val) == 2: converted = val elif isinstance(val, int): converted = (val, val+1) else: raise ValueError("Expected indexed positive value of lenght 2, integer or range of step 1") return converted
[docs] def contains(self, val): """ Check if given value or range is present. Parameters ---------- val : int or tuple or list or range Range or integer being checked. Returns ------- retlen : int Length of overlapping with `val` subranges. """ (start, end) = self.__val_convert(val) # conversion retlen = 0 for r in self.__has: if start < r[1] and end > r[0]: retlen += ((end < r[1] and end) or r[1]) - ((start > r[0] and start) or r[0]) return retlen
[docs] def __contains__(self, val): """ Method which allows ``in`` operator usage. Parameters ---------- val : int or tuple or list or range Range or integer being checked. Returns ------- bool ``True`` if **whole** examined range is present in object. Otherwise ``False``. """ conv = self.__val_convert(val) # conversion return self.contains(val) == conv[1] - conv[0]
[docs] def match(self, val): """ Search for overlapping with `val` subranges. In fact, it is a visible wrapper of hidden ``__match_l``. Parameters ---------- val : int or tuple or list or range Range or integer being checked. Returns ------- set Set of overlapping subranges. """ conv = self.__val_convert(val) # conversion return self.__match_l(conv, self.__has)
[docs] def toset(self): """ Convert object to a set of subranges. Returns ------- set ``self.__has`` is returned. """ return self.__has
def __add(self, val): """ Helper method for range addition. It is allowed to add only one compact subrange or ``range_t`` object at once. Parameters ---------- val : int or tuple or list or range Integer or range to add. Returns ------- __has : set ``self.__has`` extended by `val`. """ if not isinstance(val, range_t): #sanitize it val = {self.__val_convert(val)} # convert to a set, coz I like it that way. else: val = val.toset() __has = deepcopy(self.__has) # simply add to a set. __has.update(val) return __has
[docs] def __add__(self, val): """ ``a + b`` operation support. Parameters ---------- val : int or tuple or list or range Integer or range to add. Returns ------- range_t New ``range_t`` object extended by `val`. """ return range_t(self.__add(val))
[docs] def __iadd__(self, val): """ ``a += b`` operation support. The difference from ``+`` is that no new object is created. Parameters ---------- val : int or tuple or list or range Integer or range to add. Returns ------- self : range_t No new object is created, the current one is extended by `val` and returned. """ self.__has = self.__add(val) self.__optimize() return self
[docs] def __sub__(self, val): """ Substracting support. Parameters ---------- val : int or tuple or list or range Integer or range to substract. Returns ------- range_t New ``range_t`` object bereft of `val`. """ if not isinstance(val, range_t): #sanitize it! val = {self.__val_convert(val)} else: val = val.toset() __has = deepcopy(self.__has) for v in val: common = self.__match_l(v, __has) # search for colliding subranges. if not common: continue # no collisions - nothing to substract. __has.difference_update(common) # we delete collisions, beacause we need to cut them. minmax = (min({l[0] for l in common}), max({r[1] for r in common})) if minmax[0] < v[0]: __has.add((minmax[0], v[0])) if minmax[1] > v[1]: __has.add((v[1], minmax[1])) # we get two, one or zero "new" subranges. __optimize is not necessary. return range_t(__has)
[docs] def __len__(self): """ Length of object. Returns ------- ret : int Sum of subranges lengths. """ ret = 0 for t in self.__has: ret += t[1] - t[0] return ret
[docs] def __eq__(self, val): """ ``=`` operator support. Parameters ---------- val : range_t ``range_t`` object for comparison. Returns ------- bool ``True`` if set of subranges in this object is identical as in `val` object. """ if not isinstance(val, range_t): raise ValueError("Expected range_t to compare.") return self.__has == val.toset()