1# Copyright 2013 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5import os 6import sys 7 8_BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 9_BINTREES_PATH = os.path.join( 10 _BASE_PATH, os.pardir, os.pardir, 'third_party', 'bintrees') 11sys.path.insert(0, _BINTREES_PATH) 12 13from bintrees import FastRBTree # pylint: disable=F0401 14 15 16class ExclusiveRangeDict(object): 17 """A class like dict whose key is a range [begin, end) of integers. 18 19 It has an attribute for each range of integers, for example: 20 [10, 20) => Attribute(0), 21 [20, 40) => Attribute(1), 22 [40, 50) => Attribute(2), 23 ... 24 25 An instance of this class is accessed only via iter_range(begin, end). 26 The instance is accessed as follows: 27 28 1) If the given range [begin, end) is not covered by the instance, 29 the range is newly created and iterated. 30 31 2) If the given range [begin, end) exactly covers ranges in the instance, 32 the ranges are iterated. 33 (See test_set() in tests/range_dict_tests.py.) 34 35 3) If the given range [begin, end) starts at and/or ends at a mid-point of 36 an existing range, the existing range is split by the given range, and 37 ranges in the given range are iterated. For example, consider a case that 38 [25, 45) is given to an instance of [20, 30), [30, 40), [40, 50). In this 39 case, [20, 30) is split into [20, 25) and [25, 30), and [40, 50) into 40 [40, 45) and [45, 50). Then, [25, 30), [30, 40), [40, 45) are iterated. 41 (See test_split() in tests/range_dict_tests.py.) 42 43 4) If the given range [begin, end) includes non-existing ranges in an 44 instance, the gaps are filled with new ranges, and all ranges are iterated. 45 For example, consider a case that [25, 50) is given to an instance of 46 [30, 35) and [40, 45). In this case, [25, 30), [35, 40) and [45, 50) are 47 created in the instance, and then [25, 30), [30, 35), [35, 40), [40, 45) 48 and [45, 50) are iterated. 49 (See test_fill() in tests/range_dict_tests.py.) 50 """ 51 class RangeAttribute(object): 52 def __init__(self): 53 pass 54 55 def __str__(self): 56 return '<RangeAttribute>' 57 58 def __repr__(self): 59 return '<RangeAttribute>' 60 61 def copy(self): # pylint: disable=R0201 62 return ExclusiveRangeDict.RangeAttribute() 63 64 def __init__(self, attr=RangeAttribute): 65 self._tree = FastRBTree() 66 self._attr = attr 67 68 def iter_range(self, begin=None, end=None): 69 if not begin: 70 begin = self._tree.min_key() 71 if not end: 72 end = self._tree.max_item()[1][0] 73 74 # Assume that self._tree has at least one element. 75 if self._tree.is_empty(): 76 self._tree[begin] = (end, self._attr()) 77 78 # Create a beginning range (border) 79 try: 80 bound_begin, bound_value = self._tree.floor_item(begin) 81 bound_end = bound_value[0] 82 if begin >= bound_end: 83 # Create a blank range. 84 try: 85 new_end, _ = self._tree.succ_item(bound_begin) 86 except KeyError: 87 new_end = end 88 self._tree[begin] = (min(end, new_end), self._attr()) 89 elif bound_begin < begin and begin < bound_end: 90 # Split the existing range. 91 new_end = bound_value[0] 92 new_value = bound_value[1] 93 self._tree[bound_begin] = (begin, new_value.copy()) 94 self._tree[begin] = (new_end, new_value.copy()) 95 else: # bound_begin == begin 96 # Do nothing (just saying it clearly since this part is confusing) 97 pass 98 except KeyError: # begin is less than the smallest element. 99 # Create a blank range. 100 # Note that we can assume self._tree has at least one element. 101 self._tree[begin] = (min(end, self._tree.min_key()), self._attr()) 102 103 # Create an ending range (border) 104 try: 105 bound_begin, bound_value = self._tree.floor_item(end) 106 bound_end = bound_value[0] 107 if end > bound_end: 108 # Create a blank range. 109 new_begin = bound_end 110 self._tree[new_begin] = (end, self._attr()) 111 elif bound_begin < end and end < bound_end: 112 # Split the existing range. 113 new_end = bound_value[0] 114 new_value = bound_value[1] 115 self._tree[bound_begin] = (end, new_value.copy()) 116 self._tree[end] = (new_end, new_value.copy()) 117 else: # bound_begin == begin 118 # Do nothing (just saying it clearly since this part is confusing) 119 pass 120 except KeyError: # end is less than the smallest element. 121 # It must not happen. A blank range [begin,end) has already been created 122 # even if [begin,end) is less than the smallest range. 123 # Do nothing (just saying it clearly since this part is confusing) 124 raise 125 126 missing_ranges = [] 127 128 prev_end = None 129 for range_begin, range_value in self._tree.itemslice(begin, end): 130 range_end = range_value[0] 131 # Note that we can assume that we have a range beginning with |begin| 132 # and a range ending with |end| (they may be the same range). 133 if prev_end and prev_end != range_begin: 134 missing_ranges.append((prev_end, range_begin)) 135 prev_end = range_end 136 137 for missing_begin, missing_end in missing_ranges: 138 self._tree[missing_begin] = (missing_end, self._attr()) 139 140 for range_begin, range_value in self._tree.itemslice(begin, end): 141 yield range_begin, range_value[0], range_value[1] 142 143 def __str__(self): 144 return str(self._tree) 145