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