1 #!/usr/bin/env python 2 3 """ 4 Sequence operations. 5 6 Copyright (C) 2015, 2016 Paul Boddie <paul@boddie.org.uk> 7 8 This program is free software; you can redistribute it and/or modify it under 9 the terms of the GNU General Public License as published by the Free Software 10 Foundation; either version 3 of the License, or (at your option) any later 11 version. 12 13 This program is distributed in the hope that it will be useful, but WITHOUT 14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 15 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 16 details. 17 18 You should have received a copy of the GNU General Public License along with 19 this program. If not, see <http://www.gnu.org/licenses/>. 20 """ 21 22 from native import _isinstance 23 24 class itemaccess: 25 26 "An abstract class providing item access." 27 28 def _check_index(self, index): 29 30 """ 31 Check the given absolute 'index', raising an IndexError if out of 32 bounds. 33 """ 34 35 if index < 0 or index >= len(self): 36 raise IndexError(index) 37 38 def __getitem__(self, index): 39 40 "Return the item or slice specified by 'index'." 41 42 # Normalise any integer indexes, converting negative indexes to positive 43 # ones. 44 45 if _isinstance(index, int): 46 index = _get_absolute_index(index, self.__len__()) 47 return self.__get_single_item__(index) 48 49 # Handle slices separately. 50 51 elif _isinstance(index, slice): 52 return self.__getslice__(index.start, index.end, index.step) 53 54 # No other kinds of objects are supported as indexes. 55 56 else: 57 raise TypeError() 58 59 def __setitem__(self, index, value): 60 61 "Set at 'index' the given 'value'." 62 63 # Normalise any integer indexes, converting negative indexes to positive 64 # ones. 65 66 if _isinstance(index, int): 67 index = _get_absolute_index(index, self.__len__()) 68 return self.__set_single_item__(index, value) 69 70 # Handle slices separately. 71 72 elif _isinstance(index, slice): 73 return self.__setslice__(index.start, index.end, value) 74 75 # No other kinds of objects are supported as indexes. 76 77 else: 78 raise TypeError() 79 80 def __getslice__(self, start, end=None, step=1): 81 82 """ 83 Return a slice of the sequence starting from the 'start' index, ending 84 before the optional 'end' (or at the end of the sequence), and providing 85 items at the frequency given by 'step' (with a default step of 1). 86 """ 87 88 if step == 0: 89 raise ValueError(step) 90 91 length = self.__len__() 92 93 # Handle a null start as the first position, otherwise normalising any 94 # start index. 95 96 if start is None: 97 start = 0 98 else: 99 start = _get_absolute_index(start, length) 100 101 # Handle a null end as the first position after the end of the sequence, 102 # otherwise normalising any end index. 103 104 if end is None: 105 end = length 106 else: 107 end = _get_absolute_index(end, length) 108 109 result = [] 110 111 while step > 0 and start < end or step < 0 and start > end: 112 result.append(self.__get_single_item__(start)) 113 start += step 114 115 return result 116 117 # Methods implemented by subclasses. 118 119 def __setslice__(self, start, end, value): 120 121 "Method to be overridden by subclasses." 122 123 pass 124 125 def __get_single_item__(self, index): 126 127 "Method to be overridden by subclasses." 128 129 return None 130 131 def __set_single_item__(self, index, value): 132 133 "Method to be overridden by subclasses." 134 135 pass 136 137 def __len__(self): 138 139 "Method to be overridden by subclasses." 140 141 return 0 142 143 class sequence(itemaccess): 144 145 "A common base class for sequence types." 146 147 def _str(self, opening, closing): 148 149 "Serialise this object with the given 'opening' and 'closing' strings." 150 151 b = buffer() 152 i = 0 153 l = self.__len__() 154 first = True 155 156 b.append(opening) 157 while i < l: 158 if first: 159 first = False 160 else: 161 b.append(", ") 162 b.append(repr(self.__get_single_item__(i))) 163 i += 1 164 b.append(closing) 165 166 return str(b) 167 168 def __contains__(self, value): 169 170 "Return whether the list contains 'value'." 171 172 # Perform a linear search of the sequence contents. 173 174 for v in self: 175 176 # Return True if the current value is equal to the specified one. 177 # Note that this is not an identity test, but an equality test. 178 179 if v == value: 180 return True 181 182 return False 183 184 def index(self, value): 185 186 "Return the index of 'value' or raise ValueError." 187 188 i = 0 189 l = len(self) 190 while i < l: 191 if self[i] == value: 192 return i 193 i += 1 194 195 raise ValueError(value) 196 197 def __eq__(self, other): 198 199 "Return whether this sequence is equal to 'other'." 200 201 # Sequences must have equal lengths to be equal. 202 203 n = self.__len__() 204 if len(other) != n: 205 return False 206 207 i = 0 208 while i < n: 209 if self.__getitem__(i) != other.__getitem__(i): 210 return False 211 i += 1 212 213 return True 214 215 def __ne__(self, other): 216 217 "Return whether this sequence is not equal to 'other'." 218 219 return not self.__eq__(other) 220 221 def __iter__(self): 222 223 "Method to be overridden by subclasses." 224 225 raise StopIteration() 226 227 def _get_absolute_index(index, length): 228 229 """ 230 Return the absolute index for 'index' given a collection having the 231 specified 'length'. 232 """ 233 234 if index < 0: 235 return length + index 236 else: 237 return index 238 239 def _max(x, y): 240 241 "Return the maximum of 'x' and 'y'." 242 243 if x >= y: 244 return x 245 else: 246 return y 247 248 def _min(x, y): 249 250 "Return the minimum of 'x' and 'y'." 251 252 if x <= y: 253 return x 254 else: 255 return y 256 257 # vim: tabstop=4 expandtab shiftwidth=4