# Copyright (c) 2006 Nathan Binkert # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer; # redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution; # neither the name of the copyright holders nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. class _neg_inf(object): '''This object always compares less than any other object''' def __repr__(self): return '' def __lt__(self, other): return type(self) != type(other) def __le__(self, other): return True def __gt__(self, other): return False def __ge__(self, other): return type(self) == type(other) def __eq__(self, other): return type(self) == type(other) def __ne__(self, other): return type(self) != type(other) neg_inf = _neg_inf() class _pos_inf(object): '''This object always compares greater than any other object''' def __repr__(self): return '' def __lt__(self, other): return False def __le__(self, other): return type(self) == type(other) def __gt__(self, other): return type(self) != type(other) def __ge__(self, other): return True def __eq__(self, other): return type(self) == type(other) def __ne__(self, other): return type(self) != type(other) pos_inf = _pos_inf() class Region(tuple): '''A region (range) of [start, end). This includes utility functions to compare overlap of regions.''' def __new__(cls, *args): if len(args) == 1: arg = args[0] if isinstance(arg, Region): return arg args = tuple(arg) if len(args) != 2: raise AttributeError, \ "Only one or two arguments allowed, %d provided" % (alen, ) return tuple.__new__(cls, args) def __repr__(self): return 'Region(%s, %s)' % (self[0], self[1]) @property def start(self): return self[0] @property def end(self): return self[1] def __contains__(self, other): '''other is region: True if self and other is fully contained within self. pos: True if other is within the region''' if isinstance(other, tuple): return self[0] <= other[0] and self[1] >= other[1] return self[0] <= other and other < self[1] def __eq__(self, other): '''other is region: True if self and other are identical. pos: True if other is within the region''' if isinstance(other, tuple): return self[0] == other[0] and self[1] == other[1] return self[0] <= other and other < self[1] # @param self is a region. # @param other is a region. # @return if self and other are not identical. def __ne__(self, other): '''other is region: true if they are not identical pos: True if other is not in the region''' if isinstance(other, tuple): return self[0] != other[0] or self[1] != other[1] return other < self[0] or self[1] <= other # @param self is a region. # @param other is a region. # @return if self is less than other and does not overlap self. def __lt__(self, other): "self completely left of other (cannot overlap)" if isinstance(other, tuple): return self[1] <= other[0] return self[1] <= other # @param self is a region. # @param other is a region. # @return if self is less than other. self may overlap other, # but not extend beyond the _end of other. def __le__(self, other): "self extends to the left of other (can overlap)" if isinstance(other, tuple): return self[0] <= other[0] return self[0] <= other # @param self is a region. # @param other is a region. # @return if self is greater than other and does not overlap other. def __gt__(self, other): "self is completely right of other (cannot overlap)" if isinstance(other, tuple): return self[0] >= other[1] return self[0] > other # @param self is a region. # @param other is a region. # @return if self is greater than other. self may overlap other, # but not extend beyond the beginning of other. def __ge__(self, other): "self ex_ends beyond other to the right (can overlap)" if isinstance(other, tuple): return self[1] >= other[1] return self[1] > other class Regions(object): '''A set of regions (ranges). Basically a region with holes. Includes utility functions to merge regions and figure out if something is in one of the regions.''' def __init__(self, *args): self.regions = [] self.extend(*args) def copy(self): copy = Regions() copy.regions.extend(self.regions) return copy def append(self, *args): self.regions.append(Region(*args)) def extend(self, *args): self.regions.extend(Region(a) for a in args) def __contains__(self, position): for region in self.regions: if position in region: return True return False def __len__(self): return len(self.regions) def __iand__(self, other): A = self.regions B = other.regions R = [] i = 0 j = 0 while i < len(self) and j < len(other): a = A[i] b = B[j] if a[1] <= b[0]: # A is completely before B. Skip A i += 1 elif a[0] <= b[0]: if a[1] <= b[1]: # A and B overlap with B not left of A and A not right of B R.append(Region(b[0], a[1])) # Advance A because nothing is left i += 1 if a[1] == b[1]: # Advance B too j += 1 else: # A and B overlap with B completely within the bounds of A R.append(Region(b[0], b[1])) # Advance only B because some of A may still be useful j += 1 elif b[1] <= a[0]: # B is completely before A. Skip B. j += 1 else: assert b[0] < a[0] if b[1] <= a[1]: # A and B overlap with A not left of B and B not right of A R.append(Region(a[0], b[1])) # Advance B because nothing is left j += 1 if a[1] == b[1]: # Advance A too i += 1 else: # A and B overlap with A completely within the bounds of B R.append(Region(a[0], a[1])) # Advance only A because some of B may still be useful i += 1 self.regions = R return self def __and__(self, other): result = self.copy() result &= other return result def __repr__(self): return 'Regions(%s)' % ([(r[0], r[1]) for r in self.regions], ) all_regions = Regions(Region(neg_inf, pos_inf)) if __name__ == '__main__': x = Regions(*((i, i + 1) for i in xrange(0,30,2))) y = Regions(*((i, i + 4) for i in xrange(0,30,5))) z = Region(6,7) n = Region(9,10) def test(left, right): print "%s == %s: %s" % (left, right, left == right) print "%s != %s: %s" % (left, right, left != right) print "%s < %s: %s" % (left, right, left < right) print "%s <= %s: %s" % (left, right, left <= right) print "%s > %s: %s" % (left, right, left > right) print "%s >= %s: %s" % (left, right, left >= right) print test(neg_inf, neg_inf) test(neg_inf, pos_inf) test(pos_inf, neg_inf) test(pos_inf, pos_inf) test(neg_inf, 0) test(neg_inf, -11111) test(neg_inf, 11111) test(0, neg_inf) test(-11111, neg_inf) test(11111, neg_inf) test(pos_inf, 0) test(pos_inf, -11111) test(pos_inf, 11111) test(0, pos_inf) test(-11111, pos_inf) test(11111, pos_inf) print x print y print x & y print z print 4 in x print 4 in z print 5 not in x print 6 not in z print z in y print n in y, n not in y