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