You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

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);