1 /** 2 * @fileOverview HashMap implementation 3 */ 4 5 /// 6 /// Hash Map implementation 7 /// 8 9 /** 10 * Hash Map 11 * 12 * @class 13 * @author Sijie Guo <sijie0413@gmail.com> 14 * @constructor 15 * 16 * @param {Function} compareFunc function used for comparasion 17 * @param {Function} hashFunc function used to compute hash value 18 * @param {Number} initialCapacity initial map capacity 19 * @param {Number} loadFactor load factor 20 */ 21 var HashMap = function(compareFunc, hashFunc, initialCapacity, loadFactor) { 22 23 var DEFAULT_INITIAL_CAPACITY = 16; 24 var MAXIMUM_CAPACITY = 1 << 30; 25 var DEFAULT_LOAD_FACTOR = 0.75; 26 27 var that = this; 28 29 var _compareFunc = compareFunc; 30 var _hashFunc = hashFunc; 31 32 var _loadFactor = (!loadFactor || loadFactor <= 0) ? DEFAULT_LOAD_FACTOR : loadFactor; 33 var _capacity = (!initialCapacity || initialCapacity <= 0) ? DEFAULT_INITIAL_CAPACITY : initilaCapacity; 34 var _threshold = _capacity * _loadFactor; 35 var _table = new Array(_capacity); 36 var _size = 0; 37 var _modCnt = 0; 38 39 40 /// 41 /// Private Class 42 /// 43 var Entry = function(hash, key, value, next) { 44 this.key = key; 45 this.value = value; 46 this.hash = hash; 47 this.next = next; 48 }; 49 50 /// 51 /// Private methods 52 /// 53 54 function size() { 55 return _size; 56 } 57 58 function isEmpty() { 59 return 0 === _size; 60 } 61 62 function getForNullKey() { 63 for (var e = _table[0]; e; e = e.next) { 64 if (null === e.key) { 65 return e.value; 66 } 67 } 68 return null; 69 } 70 71 function get(key) { 72 if (null === key) { 73 return getForNullKey(); 74 } 75 var hash = HashMap.hash(_hashFunc(key)); 76 for (var e = _table[HashMap.indexFor(hash, _capacity)]; e; e = e.next) { 77 var k = e.key; 78 if (e.hash === hash && 0 === _compareFunc(k, key)) { 79 return e.value; 80 } 81 } 82 return null; 83 } 84 85 function getEntry(key) { 86 var hash = (null === key) ? 0 : HashMap.hash(_hashFunc(key)); 87 for (var e = _table[HashMap.indexFor(hash, _capacity)]; e; e = e.next) { 88 if (e.hash === hash && 0 === _compareFunc(e.key, key)) { 89 return e; 90 } 91 } 92 return null; 93 } 94 95 function putForNullKey(value) { 96 for (var e = _table[0]; e; e = e.next) { 97 if (null === e.key) { 98 var oldValue = e.value; 99 e.value = value; 100 return oldValue; 101 } 102 } 103 ++_modCnt; 104 addEntry(0, null, value, 0); 105 return null; 106 } 107 108 function resize(newCapacity) { 109 var oldTable = _table; 110 var oldCapacity = _capacity; 111 112 var newTable = new Array(newCapacity); 113 newTable = transfer(newTable); 114 115 _table = newTable; 116 _capacity = newCapacity; 117 _threshold = _capacity * _loadFactor; 118 } 119 120 function transfer(newTable) { 121 var src = _table; 122 var newCapacity = newTable.length; 123 var oldCapacity = _table.length; 124 for (var i = 0; i < oldCapacity; i++) { 125 if (src[i]) { 126 var e = src[i]; 127 do { 128 var next = e.next; 129 var idx = HashMap.indexFor(e.hash, newCapacity); 130 e.next = newTable[i]; 131 newTable[i] = e; 132 e = next; 133 } while (e); 134 } 135 } 136 return newTable; 137 } 138 139 function addEntry(hash, key, value, bucketIndex) { 140 var e = _table[bucketIndex]; 141 _table[bucketIndex] = new Entry(hash, key, value, e); 142 if (_size++ >= _threshold) { 143 resize(2 * _table.length); 144 } 145 } 146 147 function removeEntryForKey(key){ 148 var hash = (null === key) ? 0 : HashMap.hash(_hashFunc(key)); 149 var idx = HashMap.indexFor(hash, _capacity); 150 var e = prev = _table[idx]; 151 while (e) { 152 var next = e.next; 153 var k = e.key; 154 if (hash === e.hash && 0 === _compareFunc(k, key)) { 155 ++_modCnt; 156 --_size; 157 if (e === prev) { 158 _table[idx] = next; 159 } else { 160 prev.next = next; 161 } 162 return e; 163 } 164 prev = e; 165 e = e.next; 166 } 167 return null; 168 } 169 170 function clear() { 171 ++_modCnt; 172 var tab = _table; 173 for (var i in tab) { 174 tab[i] = null; 175 } 176 _size = 0; 177 } 178 this.clear = clear; 179 180 /// 181 /// Privileged methods 182 /// 183 184 this.size = size; 185 186 this.isEmpty = isEmpty; 187 188 this.get = get; 189 190 this.containsKey = function(key) { 191 return null !== getEntry(key); 192 }; 193 194 this.put = function(key, value) { 195 if (null === key) { 196 return putForNullKey(value); 197 } 198 199 var hash = HashMap.hash(_hashFunc(key)); 200 var i = HashMap.indexFor(hash, _capacity); 201 202 for (var e = _table[i]; e; e = e.next) { 203 if (hash === e.hash && 0 === _compareFunc(e.key, key)) { 204 var oldValue = e.value; 205 e.value = value; 206 return oldValue; 207 } 208 } 209 210 ++_modCnt; 211 addEntry(hash, key, value, i); 212 return null; 213 }; 214 215 this.remove = function(key) { 216 var e = removeEntryForKey(key); 217 return null === e ? null : e.value; 218 }; 219 220 }; 221 222 /** 223 * Applies a supplemental hash function to a given hashcode, which defends 224 * against poor quality hash functions. 225 * 226 * @param {Number} h a given hash code 227 * 228 * @return hash value 229 */ 230 HashMap.hash = function(h) { 231 h ^= (h >>> 20) ^ (h >>> 12); 232 return h ^ (h >>> 7) ^ (h >>> 4); 233 }; 234 235 /** 236 * Return index for hash code h 237 * 238 * @param {Number} h a given hash code 239 * @param {Number} length length of current size 240 * 241 * @return index 242 */ 243 HashMap.indexFor = function(h, length) { 244 return h & (length - 1); 245 }; 246 247 exports.HashMap = HashMap; 248 249 250