1 #!/usr/bin/env python 2 3 """ 4 Dictionary objects. 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 __builtins__.iterator import itemiterator 23 from __builtins__.span import _max 24 from native import isinstance as _isinstance 25 26 class dict: 27 28 "A dictionary representation mapping keys to values." 29 30 MISSING = object() 31 32 def __init__(self, args=None): 33 34 "Initialise the dictionary." 35 36 self.clear() 37 38 if args is not None: 39 for key, value in args: 40 self.__setitem__(key, value) 41 42 def __str__(self): 43 44 "Return a string representation." 45 46 b = buffer() 47 b.append("{") 48 49 first = True 50 51 for key, value in self.items(): 52 if first: 53 first = False 54 else: 55 b.append(", ") 56 b.append(key.__repr__()) 57 b.append(" : ") 58 b.append(value.__repr__()) 59 60 b.append("}") 61 return str(b) 62 63 __repr__ = __str__ 64 65 def _get_buckets(self, capacity): 66 67 """ 68 Reserve an attribute for a hashtable reference along with some space 69 for elements. 70 """ 71 72 buckets = [] 73 capacity = _max(capacity, 5) 74 i = 0 75 76 while i < capacity: 77 buckets.append([]) 78 i += 1 79 80 return buckets 81 82 def _get_entry(self, key): 83 84 "Return the index and entry index as a tuple for 'key'." 85 86 # Find an index identifying the bucket involved. 87 88 index = self._get_index(key) 89 90 # Find the entry index within the bucket of the key. 91 92 i = self._find_entry(key, index) 93 94 return index, i 95 96 def _get_index(self, key): 97 98 "Check 'key' and return an index or raise TypeError." 99 100 index = key.__hash__() 101 102 if not _isinstance(index, int): 103 raise TypeError 104 105 return index % len(self.buckets) 106 107 def _find_entry(self, key, index): 108 109 "Search for 'key', using an 'index' identifying the bucket involved." 110 111 i = 0 112 113 for found, value in self.buckets[index]: 114 if found == key: 115 return i 116 i += 1 117 118 return None 119 120 def _resize(self, capacity): 121 122 "Resize the hashtable to have the given 'capacity'." 123 124 buckets = self._get_buckets(capacity) 125 126 for key, value in self.items(): 127 self._setitem(buckets, key, value) 128 129 self.buckets = buckets 130 131 def _setitem(self, buckets, key, value): 132 133 "Set in the 'buckets' an item having the given 'key' and 'value'." 134 135 index, i = self._get_entry(key) 136 137 # With no existing entry, append to the bucket. 138 139 if i is None: 140 buckets[index].append((key, value)) 141 self.size += 1 142 143 # With an existing entry, replace the item. 144 145 else: 146 buckets[index][i] = key, value 147 148 # Public special methods. 149 150 def __delitem__(self, key): 151 152 "Remove the entry associated with the given 'key' from this dictionary." 153 154 index, i = self._get_entry(key) 155 156 if index is None or i is None: 157 raise KeyError, key 158 159 del self.buckets[index][i] 160 self.size -= 1 161 162 def __getitem__(self, key): 163 164 "Return the value associated with 'key' from the dictionary." 165 166 return self.get(key, self.MISSING) 167 168 def __iter__(self): 169 170 "Return an iterator." 171 172 return itemiterator(self.keys()) 173 174 def __setitem__(self, key, value): 175 176 "Set a mapping from 'key' to 'value' in the dictionary." 177 178 capacity = len(self.buckets) 179 180 if self.size > capacity: 181 self._resize(capacity * 2) 182 183 self._setitem(self.buckets, key, value) 184 185 # Public conventional methods. 186 187 def clear(self): 188 189 "Reset the dictionary to an empty state." 190 191 self.size = 0 192 self.buckets = self._get_buckets(0) 193 194 def get(self, key, default=None): 195 196 """ 197 Return the value stored for 'key'. If 'key' does not have an entry in 198 the dictionary, 'default' will be returned instead. 199 """ 200 201 index, i = self._get_entry(key) 202 203 # With no entry index, either raise an exception or return the default. 204 205 if i is None: 206 if default is self.MISSING: 207 raise KeyError(key) 208 else: 209 return default 210 211 # With a valid entry index, obtain the corresponding value. 212 213 else: 214 return self.buckets[index][i][1] 215 216 def has_key(self, key): 217 218 "Return whether the given 'key' is used with this dictionary." 219 220 return self.get(key) and True or False 221 222 def keys(self): 223 224 "Return the keys for this dictionary." 225 226 l = [] 227 for key, value in self.items(): 228 l.append(key) 229 return l 230 231 def items(self): 232 233 "Return the items, each being a (key, value) tuple, in this dictionary." 234 235 l = [] 236 for bucket in self.buckets: 237 l += bucket 238 return l 239 240 def setdefault(self, key, value): pass 241 242 def update(self, other): pass 243 244 def values(self): 245 246 "Return the values in this dictionary." 247 248 l = [] 249 for key, value in self.items(): 250 l.append(value) 251 return l 252 253 # vim: tabstop=4 expandtab shiftwidth=4