11/**
22 * Hash Map data structure implementation
3+ *
4+ * Features:
5+ * - HashMap offers 0(1) lookup and insertion.
6+ * - Keys are ordered by their insertion order (like LinkedHashMap)
7+ * - It contains only unique elements.
8+ * - It may have one null key and multiple null values.
9+ *
310 * @author Adrian Mejia <me AT adrianmejia.com>
411 */
512class HashMap {
613 /**
7- * Initialize array that holds the values. Default is size 1,000
14+ * Initialize array that holds the values. Default is size 16
815 * @param {number } initialCapacity initial size of the array
916 * @param {number } loadFactor if set, the Map will automatically rehash when the load factor threshold is met
1017 */
11- constructor ( initialCapacity = 1000 , loadFactor = 0 ) {
18+ constructor ( initialCapacity = 16 , loadFactor = 0.75 ) {
1219 this . buckets = new Array ( initialCapacity ) ;
1320 this . loadFactor = loadFactor ;
1421 this . size = 0 ;
1522 this . collisions = 0 ;
16- this . keys = [ ] ;
23+ this . keysArrayWrapper = [ ] ;
1724 }
1825
1926 /**
2027 * Decent hash function where each char ascii code is added with an offset depending on the possition
2128 * @param {any } key
2229 */
23- hash ( key ) {
30+ static hashCode ( key ) {
2431 let hashValue = 0 ;
2532 const stringTypeKey = `${ key } ${ typeof key } ` ;
2633
@@ -33,11 +40,12 @@ class HashMap {
3340 }
3441
3542 /**
36- * Get the array index after applying the hash funtion to the given key
43+ * A hash function converts keys into array indices
3744 * @param {any } key
45+ * @returns {Number } array index given the bucket size
3846 */
39- _getBucketIndex ( key ) {
40- const hashValue = this . hash ( key ) ;
47+ hashFunction ( key ) {
48+ const hashValue = HashMap . hashCode ( key ) ;
4149 const bucketIndex = hashValue % this . buckets . length ;
4250 return bucketIndex ;
4351 }
@@ -53,7 +61,7 @@ class HashMap {
5361
5462 if ( entryIndex === undefined ) {
5563 // initialize array and save key/value
56- const keyIndex = this . keys . push ( { content : key } ) - 1 ; // keep track of the key index
64+ const keyIndex = this . keysArrayWrapper . push ( { content : key } ) - 1 ; // keep track of the key index
5765 this . buckets [ bucketIndex ] = this . buckets [ bucketIndex ] || [ ] ;
5866 this . buckets [ bucketIndex ] . push ( { key, value, keyIndex } ) ;
5967 this . size ++ ;
@@ -92,7 +100,7 @@ class HashMap {
92100 * @param {any } key
93101 */
94102 has ( key ) {
95- return ! ! this . get ( key ) ;
103+ return this . _getIndexes ( key ) . entryIndex !== undefined ;
96104 }
97105
98106 /**
@@ -101,13 +109,13 @@ class HashMap {
101109 * @param {any } key
102110 */
103111 _getIndexes ( key ) {
104- const bucketIndex = this . _getBucketIndex ( key ) ;
112+ const bucketIndex = this . hashFunction ( key ) ;
105113 const values = this . buckets [ bucketIndex ] || [ ] ;
106114
107115 for ( let entryIndex = 0 ; entryIndex < values . length ; entryIndex ++ ) {
108116 const entry = values [ entryIndex ] ;
109117 if ( entry . key === key ) {
110- return { bucketIndex, entryIndex } ;
118+ return { bucketIndex, entryIndex, keyIndex : entry . keyIndex } ;
111119 }
112120 }
113121
@@ -126,7 +134,7 @@ class HashMap {
126134 }
127135
128136 this . buckets [ bucketIndex ] . splice ( entryIndex , 1 ) ;
129- delete this . keys [ keyIndex ] ;
137+ delete this . keysArrayWrapper [ keyIndex ] ;
130138 this . size -- ;
131139
132140 return true ;
@@ -139,110 +147,59 @@ class HashMap {
139147 rehash ( newCapacity ) {
140148 const newMap = new HashMap ( newCapacity ) ;
141149
142- this . keys . forEach ( ( key ) => {
143- if ( key ) {
144- newMap . set ( key . content , this . get ( key . content ) ) ;
145- }
150+ this . keysArrayWrapper . forEach ( ( key ) => {
151+ newMap . set ( key . content , this . get ( key . content ) ) ;
146152 } ) ;
147153
148154 // update bucket
149155 this . buckets = newMap . buckets ;
150156 this . collisions = newMap . collisions ;
151157 // Optional: both `keys` has the same content except that the new one doesn't have empty spaces from deletions
152- this . keys = newMap . keys ;
158+ this . keysArrayWrapper = newMap . keysArrayWrapper ;
153159 }
154160
155161 /**
156- * Load factor - measure how full the Map is. It's ratio between items on the map and total size of buckets
162+ * Load factor - measure how full the Map is.
163+ * It's ratio between items on the map and total size of buckets
157164 */
158165 getLoadFactor ( ) {
159166 return this . size / this . buckets . length ;
160167 }
168+
169+ /**
170+ * Returns an array with valid keys
171+ * If keys has been deleted they shouldn't be in the array of keys
172+ */
173+ keys ( ) {
174+ return this . keysArrayWrapper . reduce ( ( acc , key ) => {
175+ acc . push ( key . content ) ;
176+ return acc ;
177+ } , [ ] ) ;
178+ }
179+
180+ /**
181+ * The values() method returns a new Iterator object that
182+ * contains the values for each element in the Map object
183+ * in insertion order.
184+ *
185+ * @example
186+ * const myMap = new HashMap();
187+ * myMap.set('0', 'foo');
188+ * myMap.set(1, 'bar');
189+ * myMap.set({}, 'baz');
190+ *
191+ * var mapIter = myMap.values();
192+ *
193+ * console.log(mapIter.next().value); // "foo"
194+ * console.log(mapIter.next().value); // "bar"
195+ * console.log(mapIter.next().value); // "baz"
196+ */
197+ values ( ) {
198+ throw new Error ( 'Not implemented' ) ;
199+ }
161200}
162201
163- module . exports = HashMap ;
202+ // Aliases
203+ HashMap . prototype . containsKey = HashMap . prototype . has ;
164204
165- /*
166- // Usage:
167- // const hashMap = new HashMap();
168- const hashMap = new HashMap(1);
169- // const hashMap = new Map();
170-
171- const assert = require('assert');
172-
173- assert.equal(hashMap.size, 0);
174- hashMap.set('cat', 2);
175- assert.equal(hashMap.size, 1);
176- hashMap.set('rat', 7);
177- hashMap.set('dog', 1);
178- hashMap.set('art', 0);
179- assert.equal(hashMap.size, 4);
180-
181- assert.equal(hashMap.get('cat'), 2);
182- assert.equal(hashMap.get('rat'), 7);
183- assert.equal(hashMap.get('dog'), 1);
184-
185- assert.equal(hashMap.has('rap'), false);
186- assert.equal(hashMap.delete('rap'), false);
187-
188- assert.equal(hashMap.has('rat'), true);
189- assert.equal(hashMap.delete('rat'), true);
190- assert.equal(hashMap.has('rat'), false);
191- assert.equal(hashMap.size, 3);
192-
193- // set override
194- assert.equal(hashMap.get('art'), 0);
195- hashMap.set('art', 2);
196- assert.equal(hashMap.get('art'), 2);
197- assert.equal(hashMap.size, 3);
198-
199- // undefined
200- hashMap.set(undefined, 'undefined type');
201- hashMap.set('undefined', 'string type');
202-
203- assert.equal(hashMap.get(undefined), 'undefined type');
204- assert.equal(hashMap.get('undefined'), 'string type');
205- assert.equal(hashMap.size, 5);
206-
207- // ----
208- // Internal structure tests
209- // ----
210- console.log(hashMap.collisions);
211- console.log(hashMap.buckets);
212-
213- assert.equal(hashMap.getLoadFactor(), 5);
214-
215- // rehash
216- hashMap.rehash(1000);
217- console.log(hashMap.collisions);
218- console.log(hashMap.buckets);
219- assert.equal(hashMap.getLoadFactor(), 5 / 1000);
220-
221- // automatic rehashing based on loadFactor
222- const dynamicMap = new HashMap(2, 0.75);
223-
224- dynamicMap.set('uno', 1);
225- assert.equal(dynamicMap.buckets.length, 2);
226- assert.equal(dynamicMap.getLoadFactor(), 1 / 2);
227- console.log(hashMap.collisions);
228-
229- dynamicMap.set('dos', 2);
230- assert.equal(dynamicMap.buckets.length, 4); // <-- rehash took place
231- assert.equal(dynamicMap.getLoadFactor(), 1 / 2);
232- console.log(hashMap.collisions);
233-
234- dynamicMap.set('tres', 3);
235- assert.equal(dynamicMap.buckets.length, 4); // <-- no rehash
236- assert.equal(dynamicMap.getLoadFactor(), 3 / 4);
237- console.log(hashMap.collisions);
238-
239- dynamicMap.set('cuatro', 4);
240- assert.equal(dynamicMap.buckets.length, 8); // <-- rehash took place
241- assert.equal(dynamicMap.getLoadFactor(), 4 / 8);
242- console.log(hashMap.collisions);
243-
244- dynamicMap.set('cinco', 5);
245- assert.equal(dynamicMap.buckets.length, 8); // <-- no rehash
246- assert.equal(dynamicMap.getLoadFactor(), 5 / 8);
247- console.log(hashMap.collisions);
248- */
205+ module . exports = HashMap ;
0 commit comments