1 var define = require("../define").define, 2 Collection = require("./Collection"), 3 Iterable = require("./Iterable"), 4 base = require("../base"); 5 6 var hashFunction = function (key) { 7 if (typeof key == "string") { 8 return key; 9 } else if (typeof key == "object") { 10 return key.hashCode ? key.hashCode() : "" + key; 11 } else { 12 return "" + key; 13 } 14 }; 15 16 var Bucket = define(null, { 17 18 instance : { 19 20 constructor : function() { 21 this.__entries = []; 22 }, 23 24 pushValue : function(key, value) { 25 this.__entries.push({key : key, value : value}); 26 return value; 27 }, 28 29 remove : function(key) { 30 var ret = null, map = this.__entries, val; 31 var i = map.length - 1; 32 for (; i >= 0; i--) { 33 if ((val = map[i]) != null && val.key == key) { 34 map[i] = null; 35 return val.value; 36 } 37 } 38 return ret; 39 }, 40 41 "set" : function(key, value) { 42 var ret = null, map = this.__entries; 43 var i = map.length - 1; 44 for (; i >= 0; i--) { 45 var val = map[i]; 46 if (val && key === val.key) { 47 val.value = value; 48 ret = value; 49 break; 50 } 51 } 52 if (!ret) { 53 map.push({key : key, value : value}); 54 } 55 return ret; 56 }, 57 58 find : function(key) { 59 var ret = null, map = this.__entries, val; 60 var i = map.length - 1; 61 for (; i >= 0; i--) { 62 val = map[i]; 63 if (val && key === val.key) { 64 ret = val.value; 65 break; 66 } 67 } 68 return ret; 69 }, 70 71 getEntrySet : function(arr) { 72 var map = this.__entries, l = map.length; 73 if (l) { 74 for (var i = 0; i < l; i++) { 75 var e = map[i]; 76 if (e) { 77 arr.push(e); 78 } 79 } 80 } 81 }, 82 83 getKeys : function(arr) { 84 var map = this.__entries, l = map.length; 85 if (l) { 86 for (var i = 0; i < l; i++) { 87 var e = map[i]; 88 if (e) { 89 arr.push(e.key); 90 } 91 } 92 } 93 return arr; 94 }, 95 96 getValues : function(arr) { 97 var map = this.__entries, l = map.length; 98 if (l) { 99 for (var i = 0; i < l; i++) { 100 var e = map[i]; 101 if (e) { 102 arr.push(e.value); 103 } 104 } 105 } 106 return arr; 107 } 108 } 109 }); 110 111 /** 112 *@class <p>Implementation of a HashTable for javascript. 113 * This HashTable implementation allows one to use anything as a key. 114 * </p> 115 * <b>NOTE: THIS IS ~ 3 times slower than javascript native objects</b> 116 * 117 * <p> A use case for this collection is when one needs to store items in which the key will not be a string, or number</p> 118 * 119 * @name HashTable 120 * @augments comb.collections.Collection 121 * @memberOf comb.collections 122 * 123 * @property {Array} keys all keys contained in the table 124 * @property {Array} values all values contained in the table 125 * @property {Array} entrySet an array of objects. Each object contains a key, and value property. 126 */ 127 module.exports = exports = HashTable = define([Collection, Iterable], { 128 129 instance : { 130 /**@lends comb.collections.HashTable.prototype*/ 131 132 constructor : function() { 133 this.__map = {}; 134 }, 135 136 __entrySet : function() { 137 var ret = [], es = []; 138 for (var i in this.__map) { 139 this.__map[i].getEntrySet(ret); 140 } 141 return ret; 142 }, 143 144 /** 145 * Put a key, value pair into the table 146 * 147 * <b>NOTE :</b> the collection will not check if the key previously existed. 148 * 149 * @param {Anything} key the key to look up the object. 150 * @param {Anything} value the value that corresponds to the key. 151 * 152 * @returns the value 153 */ 154 put : function(key, value) { 155 var hash = hashFunction(key); 156 var bucket = null; 157 if ((bucket = this.__map[hash]) == null) { 158 bucket = (this.__map[hash] = new Bucket()); 159 } 160 bucket.pushValue(key, value); 161 return value; 162 }, 163 164 /** 165 * Remove a key value pair from the table. 166 * 167 * @param key the key of the key value pair to remove. 168 * 169 * @returns the removed value. 170 */ 171 remove : function(key) { 172 var hash = hashFunction(key), ret = null; 173 var bucket = this.__map[hash]; 174 if (bucket) { 175 ret = bucket.remove(key); 176 } 177 return ret; 178 }, 179 180 /** 181 * Get the value corresponding to the key. 182 * 183 * @param key the key used to look up the value 184 * 185 * @returns null if not found, or the value. 186 */ 187 "get" : function(key) { 188 var hash = hashFunction(key), ret = null; 189 var bucket = null; 190 if ((bucket = this.__map[hash]) != null) { 191 ret = bucket.find(key); 192 } 193 return ret; 194 }, 195 196 /** 197 * Set the value of a previously existing key,value pair or create a new entry. 198 * 199 * @param key the key to be be used 200 * @param value the value to be set 201 * 202 * @returns the value. 203 */ 204 "set" : function(key, value) { 205 var hash = hashFunction(key), ret = null, bucket = null, map = this.__map; 206 if ((bucket = map[hash]) != null) { 207 ret = bucket.set(key, value); 208 } else { 209 ret = (map[hash] = new Bucket()).pushValue(key, value); 210 } 211 return ret; 212 }, 213 214 /** 215 * Tests if the table contains a particular key 216 * @param key the key to test 217 * 218 * @returns {Boolean} true if it exitsts false otherwise. 219 */ 220 contains : function(key) { 221 var hash = hashFunction(key), ret = false; 222 var bucket = null; 223 if ((bucket = this.__map[hash]) != null) { 224 ret = bucket.find(key) != null; 225 } 226 return ret; 227 }, 228 229 /** 230 * Returns a new HashTable containing the values of this HashTable, and the other table. 231 * </br> 232 * <b> DOES NOT CHANGE THE ORIGINAL!</b> 233 * @param {comb.collections.HashTable} hashTable the hash table to concat with this. 234 * 235 * @returns {comb.collections.HashTable} a new HashTable containing all values from both tables. 236 */ 237 concat : function(hashTable) { 238 if (hashTable instanceof HashTable) { 239 var ret = new HashTable(); 240 var otherEntrySet = hashTable.entrySet.concat(this.entrySet); 241 for (var i = otherEntrySet.length - 1; i >= 0; i--) { 242 var e = otherEntrySet[i]; 243 ret.put(e.key, e.value); 244 } 245 return ret; 246 } else { 247 throw new TypeError("When joining hashtables the joining arg must be a HashTable"); 248 } 249 }, 250 251 /** 252 * Creates a new HashTable containg values that passed the filtering function. 253 * 254 * @param {Function} cb Function to callback with each item, the first aruguments is an object containing a key and value field 255 * @param {Object} scope the scope to call the function. 256 * 257 * @returns {comb.collections.HashTable} the HashTable containing the values that passed the filter. 258 */ 259 filter : function(cb, scope) { 260 var es = this.__entrySet(), ret = new HashTable(); 261 es = es.filter.apply(es, arguments); 262 for (var i = es.length - 1; i >= 0; i--) { 263 var e = es[i]; 264 ret.put(e.key, e.value); 265 } 266 return ret; 267 }, 268 269 /** 270 * Loop through each value in the hashtable 271 * 272 * @param {Function} cb the function to call with an object containing a key and value field 273 * @param {Object} scope the scope to call the funciton in 274 */ 275 forEach : function(cb, scope) { 276 var es = this.__entrySet(), l = es.length, f = cb.bind(scope || this); 277 es.forEach.apply(es, arguments); 278 }, 279 280 /** 281 * Determines if every item meets the condition returned by the callback. 282 * 283 * @param {Function} cb Function to callback with each item, the first aruguments is an object containing a key and value field 284 * @param {Object} [scope=this] scope to call the function in 285 * 286 * @returns {Boolean} True if every item passed false otherwise 287 */ 288 every : function() { 289 var es = this.__entrySet(); 290 return es.every.apply(es, arguments); 291 }, 292 293 /** 294 * Loop through each value in the hashtable, collecting the value returned by the callback function. 295 * @param {Function} cb Function to callback with each item, the first aruguments is an object containing a key and value field 296 * @param {Object} [scope=this] scope to call the function in 297 * 298 * @returns {Array} an array containing the mapped values. 299 */ 300 map : function() { 301 var es = this.__entrySet(), ret = new HashTable(); 302 return es.map.apply(es, arguments); 303 }, 304 305 /** 306 * Determines if some items meet the condition returned by the callback. 307 * 308 * @param {Function} cb Function to callback with each item, the first aruguments is an object containing a key and value field 309 * @param {Object} [scope=this] scope to call the function in 310 * 311 * @returns {Boolean} True if some items passed false otherwise 312 */ 313 some : function() { 314 var es = this.__entrySet(); 315 return es.some.apply(es, arguments); 316 }, 317 318 /** 319 * Apply a function against an accumulator and each value of the array (from left-to-right) as to reduce it to a single value. 320 * 321 * @param {Function} callback Function to execute on each value in the array. 322 * @param initialValue Value to use as the first argument to the first call of the callback.. 323 */ 324 reduce : function() { 325 var es = this.__entrySet(); 326 return es.reduce.apply(es, arguments); 327 }, 328 329 /** 330 * Apply a function against an accumulator and each value of the array (from right-to-left) as to reduce it to a single value. 331 * 332 * @param {Function} callback Function to execute on each value in the array. 333 * @param initialValue Value to use as the first argument to the first call of the callback.. 334 */ 335 reduceRight : function() { 336 var es = this.__entrySet(); 337 return es.reduceRight.apply(es, arguments); 338 }, 339 340 /** 341 * Clears out all items from the table. 342 */ 343 clear : function() { 344 this.__map = {}; 345 }, 346 347 getters : { 348 keys : function() { 349 var ret = [], es = []; 350 for (var i in this.__map) { 351 this.__map[i].getKeys(ret); 352 } 353 return ret; 354 }, 355 356 values : function() { 357 var ret = [], es = []; 358 for (var i in this.__map) { 359 this.__map[i].getValues(ret); 360 } 361 return ret; 362 }, 363 364 entrySet : function() { 365 return this.__entrySet(); 366 }, 367 368 isEmpty : function() { 369 return this.keys.length == 0; 370 } 371 } 372 } 373 374 }); 375 376 377 378