      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