256 lines
6.7 KiB
JavaScript
256 lines
6.7 KiB
JavaScript
/**
|
|
* 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);
|