/** * Hash Table * * An associative array, that can map keys * (strings and numbers) to values in O(1). * * @example * var hash = require('path-to-algorithms/src/data-structures'+ * '/hash-table'); * var hashTable = new hash.Hashtable(); * * hashTable.put(10, 'value'); * hashTable.put('key', 10); * * console.log(hashTable.get(10)); // 'value' * console.log(hashTable.get('key')); // 10 * * hashTable.remove(10); * hashTable.remove('key'); * * console.log(hashTable.get(10)); // undefined * console.log(hashTable.get('key')); // undefined * * @module data-structures/hash-table */ (function (exports) { 'use strict'; /** * Constructs a Node to store data and next/prev nodes in Hash table. * * @public * @constructor * @param {Number|String} key Key of the node. * @param {Number|String} data Data to be stored in hash table. */ exports.Node = function (key, data) { this.key = key; this.data = data; this.next = undefined; this.prev = undefined; }; /** * Construct a Hash table.. * * @public * @constructor */ exports.Hashtable = function () { this.buckets = []; // The higher the bucket count; less likely for collisions. this.maxBucketCount = 100; }; /** * Simple non-crypto hash used to hash keys, which determines * while bucket the value will be placed in. * A javascript implementation of Java's 32bitint hash. * * @public * @method * @param {Number|String} val Key to be hashed. */ exports.Hashtable.prototype.hashCode = function (val) { var i; var hashCode = 0; var character; // If value to be hashed is already an integer, return it. if (val.length === 0 || val.length === undefined) { return val; } for (i = 0; i < val.length; i += 1) { character = val.charCodeAt(i); /*jshint -W016 */ hashCode = ((hashCode << 5) - hashCode) + character; hashCode = hashCode & hashCode; /*jshint -W016 */ } return hashCode; }; /** * Puts data into the table based on hashed key value. * * @public * @method * @param {Number|String} key Key for data. * @param {Number|String} data Data to be stored in table. */ exports.Hashtable.prototype.put = function (key, data, hashCode) { /* Make collision testing easy with optional hashCode parameter. That should not be used! Only by spec/tests. */ if (hashCode === undefined) { // Typical use hashCode = this.hashCode(key); } else if (hashCode.length > 0) { // Testing/Spec - String hash passed, convert to int based hash. hashCode = this.hashCode(hashCode); } // Adjust hash to fit within buckets. hashCode = hashCode % this.maxBucketCount; var newNode = new exports.Node(key, data); // No element exists at hash/index for given key -> put in table. if (this.buckets[hashCode] === undefined) { this.buckets[hashCode] = newNode; return; } // Element exists at hash/index for given key, but, same key -> overwrite. if (this.buckets[hashCode].key === key) { this.buckets[hashCode].data = data; return; } /* Item exists at hash/index for key, but different key. Handle collision. */ var first = this.buckets[hashCode]; while (first.next !== undefined) { first = first.next; } first.next = newNode; newNode.prev = first; }; /** * Get's data from the table based on key. * * @public * @method * @param {Number|String} key Key for data to be retrieved. */ exports.Hashtable.prototype.get = function (key, hashCode) { /* Make collision testing easy with optional hashCode parameter. That should not be used! Only by spec/tests. */ if (hashCode === undefined) { // Typical use hashCode = this.hashCode(key); } else if (hashCode.length > 0) { // Testing/Spec - String hash passed, convert to int based hash. hashCode = this.hashCode(hashCode); } hashCode = hashCode % this.maxBucketCount; if (this.buckets[hashCode] === undefined) { return undefined; } else if ( this.buckets[hashCode].next === undefined && this.buckets[hashCode].key === key ) { return this.buckets[hashCode].data; } else { var first = this.buckets[hashCode]; while ( first !== undefined && first.next !== undefined && first.key !== key ) { first = first.next; } if (first.key === key) { return first.data; } else { return undefined; } } }; /** * Removes data from the table based on key. * * @public * @method * @param {Number|String} key Key of the data to be removed. */ exports.Hashtable.prototype.remove = function (key, hashCode) { /* Make collision testing easy with optional hashCode parameter. That should not be used! Only by spec/tests. */ if (hashCode === undefined) { // Typical use hashCode = this.hashCode(key); } else if (hashCode.length > 0) { // Testing/Spec - String hash passed, convert to int based hash. hashCode = this.hashCode(hashCode); } hashCode = hashCode % this.maxBucketCount; if (this.buckets[hashCode] === undefined) { return undefined; } else if (this.buckets[hashCode].next === undefined) { this.buckets[hashCode] = undefined; } else { var first = this.buckets[hashCode]; while ( first !== undefined && first.next !== undefined && first.key !== key ) { first = first.next; } var removedValue = first.data; // Removing (B) // (B) : only item in bucket if (first.prev === undefined && first.next === undefined) { first = undefined; return removedValue; } // (B) - A - C: start link in bucket if (first.prev === undefined && first.next !== undefined) { first.data = first.next.data; first.key = first.next.key; if (first.next.next !== undefined) { first.next = first.next.next; } else { first.next = undefined; } return removedValue; } // A - (B) : end link in bucket if (first.prev !== undefined && first.next === undefined) { first.prev.next = undefined; first = undefined; return removedValue; } // A - (B) - C : middle link in bucket if (first.prev !== undefined && first.next !== undefined) { first.prev.next = first.next; first.next.prev = first.prev; first = undefined; return removedValue; } } }; })(typeof window === 'undefined' ? module.exports : window);