diff --git a/hashset.js b/hashset.js index d65da5d..c323a48 100644 --- a/hashset.js +++ b/hashset.js @@ -18,8 +18,8 @@ * HashSet * * This is a JavaScript implementation of HashSet, similar in concept to those found in Java or C#'s standard libraries. - * It is distributed as part of jshashtable and depends on jshashtable.js. It creates a single constructor function - * called HashSet in the global scope. + * It is distributed as part of jshashtable and depends on jshashtable.js. It exports a single constructor function + * called HashSet. * * Depends on: jshashtable.js * Author: Tim Down @@ -28,92 +28,104 @@ * Website: http://www.timdown.co.uk/jshashtable/ */ -function HashSet(param1, param2) { - var hashTable = new Hashtable(param1, param2); - - this.add = function(o) { - hashTable.put(o, true); - }; - - this.addAll = function(arr) { - for (var i = 0, len = arr.length; i < len; ++i) { - hashTable.put(arr[i], true); - } - }; - - this.values = function() { - return hashTable.keys(); - }; - - this.remove = function(o) { - return hashTable.remove(o) ? o : null; - }; - - this.contains = function(o) { - return hashTable.containsKey(o); - }; - - this.clear = function() { - hashTable.clear(); - }; - - this.size = function() { - return hashTable.size(); - }; - - this.isEmpty = function() { - return hashTable.isEmpty(); - }; - - this.clone = function() { - var h = new HashSet(param1, param2); - h.addAll(hashTable.keys()); - return h; - }; - - this.intersection = function(hashSet) { - var intersection = new HashSet(param1, param2); - var values = hashSet.values(), i = values.length, val; - while (i--) { - val = values[i]; - if (hashTable.containsKey(val)) { - intersection.add(val); +(function(root, factory) { + if (typeof define === 'function' && define.amd) { + define(['./Hashtable'], factory); + } else if (typeof module === 'object' && module.exports) { + module.exports = factory(require('./Hashtable')); + } else { + root.HashSet = factory(root.Hashtable); + } +}(this, function(Hashtable) { + function HashSet(param1, param2) { + var hashTable = new Hashtable(param1, param2); + + this.add = function(o) { + hashTable.put(o, true); + }; + + this.addAll = function(arr) { + for (var i = 0, len = arr.length; i < len; ++i) { + hashTable.put(arr[i], true); } - } - return intersection; - }; - - this.union = function(hashSet) { - var union = this.clone(); - var values = hashSet.values(), i = values.length, val; - while (i--) { - val = values[i]; - if (!hashTable.containsKey(val)) { - union.add(val); + }; + + this.values = function() { + return hashTable.keys(); + }; + + this.remove = function(o) { + return hashTable.remove(o) ? o : null; + }; + + this.contains = function(o) { + return hashTable.containsKey(o); + }; + + this.clear = function() { + hashTable.clear(); + }; + + this.size = function() { + return hashTable.size(); + }; + + this.isEmpty = function() { + return hashTable.isEmpty(); + }; + + this.clone = function() { + var h = new HashSet(param1, param2); + h.addAll(hashTable.keys()); + return h; + }; + + this.intersection = function(hashSet) { + var intersection = new HashSet(param1, param2); + var values = hashSet.values(), i = values.length, val; + while (i--) { + val = values[i]; + if (hashTable.containsKey(val)) { + intersection.add(val); + } } - } - return union; - }; - - this.isSubsetOf = function(hashSet) { - var values = hashTable.keys(), i = values.length; - while (i--) { - if (!hashSet.contains(values[i])) { - return false; + return intersection; + }; + + this.union = function(hashSet) { + var union = this.clone(); + var values = hashSet.values(), i = values.length, val; + while (i--) { + val = values[i]; + if (!hashTable.containsKey(val)) { + union.add(val); + } } - } - return true; - }; - - this.complement = function(hashSet) { - var complement = new HashSet(param1, param2); - var values = this.values(), i = values.length, val; - while (i--) { - val = values[i]; - if (!hashSet.contains(val)) { - complement.add(val); + return union; + }; + + this.isSubsetOf = function(hashSet) { + var values = hashTable.keys(), i = values.length; + while (i--) { + if (!hashSet.contains(values[i])) { + return false; + } } - } - return complement; - }; -} + return true; + }; + + this.complement = function(hashSet) { + var complement = new HashSet(param1, param2); + var values = this.values(), i = values.length, val; + while (i--) { + val = values[i]; + if (!hashSet.contains(val)) { + complement.add(val); + } + } + return complement; + }; + } + + return HashSet; +} ) ); \ No newline at end of file diff --git a/hashtable.js b/hashtable.js index edb5a46..35810ee 100644 --- a/hashtable.js +++ b/hashtable.js @@ -1,6 +1,6 @@ /** - * @license jahashtable, a JavaScript implementation of a hash table. It creates a single constructor function called - * Hashtable in the global scope. + * @license jahashtable, a JavaScript implementation of a hash table. It exports a single constructor function called + * Hashtable. * * http://www.timdown.co.uk/jshashtable/ * Copyright %%build:year%% Tim Down. @@ -19,384 +19,396 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -var Hashtable = (function(UNDEFINED) { - var FUNCTION = "function", STRING = "string", UNDEF = "undefined"; - - // Require Array.prototype.splice, Object.prototype.hasOwnProperty and encodeURIComponent. In environments not - // having these (e.g. IE <= 5), we bail out now and leave Hashtable null. - if (typeof encodeURIComponent == UNDEF || - Array.prototype.splice === UNDEFINED || - Object.prototype.hasOwnProperty === UNDEFINED) { - return null; +(function(root, factory) { + if (typeof define === 'function' && define.amd) { + define([], factory); + } else if (typeof module === 'object' && module.exports) { + module.exports = factory(); + } else { + root.Hashtable = factory(); } +}(this, function() { + var Hashtable = (function(UNDEFINED) { + var FUNCTION = "function", STRING = "string", UNDEF = "undefined"; + + // Require Array.prototype.splice, Object.prototype.hasOwnProperty and encodeURIComponent. In environments not + // having these (e.g. IE <= 5), we bail out now and leave Hashtable null. + if (typeof encodeURIComponent == UNDEF || + Array.prototype.splice === UNDEFINED || + Object.prototype.hasOwnProperty === UNDEFINED) { + return null; + } - function toStr(obj) { - return (typeof obj == STRING) ? obj : "" + obj; - } + function toStr(obj) { + return (typeof obj == STRING) ? obj : "" + obj; + } - function hashObject(obj) { - var hashCode; - if (typeof obj == STRING) { - return obj; - } else if (typeof obj.hashCode == FUNCTION) { - // Check the hashCode method really has returned a string - hashCode = obj.hashCode(); - return (typeof hashCode == STRING) ? hashCode : hashObject(hashCode); - } else { - return toStr(obj); + function hashObject(obj) { + var hashCode; + if (typeof obj == STRING) { + return obj; + } else if (typeof obj.hashCode == FUNCTION) { + // Check the hashCode method really has returned a string + hashCode = obj.hashCode(); + return (typeof hashCode == STRING) ? hashCode : hashObject(hashCode); + } else { + return toStr(obj); + } } - } - - function merge(o1, o2) { - for (var i in o2) { - if (o2.hasOwnProperty(i)) { - o1[i] = o2[i]; + + function merge(o1, o2) { + for (var i in o2) { + if (o2.hasOwnProperty(i)) { + o1[i] = o2[i]; + } } } - } - function equals_fixedValueHasEquals(fixedValue, variableValue) { - return fixedValue.equals(variableValue); - } + function equals_fixedValueHasEquals(fixedValue, variableValue) { + return fixedValue.equals(variableValue); + } - function equals_fixedValueNoEquals(fixedValue, variableValue) { - return (typeof variableValue.equals == FUNCTION) ? - variableValue.equals(fixedValue) : (fixedValue === variableValue); - } + function equals_fixedValueNoEquals(fixedValue, variableValue) { + return (typeof variableValue.equals == FUNCTION) ? + variableValue.equals(fixedValue) : (fixedValue === variableValue); + } - function createKeyValCheck(kvStr) { - return function(kv) { - if (kv === null) { - throw new Error("null is not a valid " + kvStr); - } else if (kv === UNDEFINED) { - throw new Error(kvStr + " must not be undefined"); - } - }; - } + function createKeyValCheck(kvStr) { + return function(kv) { + if (kv === null) { + throw new Error("null is not a valid " + kvStr); + } else if (kv === UNDEFINED) { + throw new Error(kvStr + " must not be undefined"); + } + }; + } - var checkKey = createKeyValCheck("key"), checkValue = createKeyValCheck("value"); + var checkKey = createKeyValCheck("key"), checkValue = createKeyValCheck("value"); - /*----------------------------------------------------------------------------------------------------------------*/ + /*----------------------------------------------------------------------------------------------------------------*/ - function Bucket(hash, firstKey, firstValue, equalityFunction) { - this[0] = hash; - this.entries = []; - this.addEntry(firstKey, firstValue); + function Bucket(hash, firstKey, firstValue, equalityFunction) { + this[0] = hash; + this.entries = []; + this.addEntry(firstKey, firstValue); - if (equalityFunction !== null) { - this.getEqualityFunction = function() { - return equalityFunction; - }; + if (equalityFunction !== null) { + this.getEqualityFunction = function() { + return equalityFunction; + }; + } } - } - var EXISTENCE = 0, ENTRY = 1, ENTRY_INDEX_AND_VALUE = 2; + var EXISTENCE = 0, ENTRY = 1, ENTRY_INDEX_AND_VALUE = 2; - function createBucketSearcher(mode) { - return function(key) { - var i = this.entries.length, entry, equals = this.getEqualityFunction(key); - while (i--) { - entry = this.entries[i]; - if ( equals(key, entry[0]) ) { - switch (mode) { - case EXISTENCE: - return true; - case ENTRY: - return entry; - case ENTRY_INDEX_AND_VALUE: - return [ i, entry[1] ]; + function createBucketSearcher(mode) { + return function(key) { + var i = this.entries.length, entry, equals = this.getEqualityFunction(key); + while (i--) { + entry = this.entries[i]; + if ( equals(key, entry[0]) ) { + switch (mode) { + case EXISTENCE: + return true; + case ENTRY: + return entry; + case ENTRY_INDEX_AND_VALUE: + return [ i, entry[1] ]; + } } } - } - return false; - }; - } + return false; + }; + } - function createBucketLister(entryProperty) { - return function(aggregatedArr) { - var startIndex = aggregatedArr.length; - for (var i = 0, entries = this.entries, len = entries.length; i < len; ++i) { - aggregatedArr[startIndex + i] = entries[i][entryProperty]; - } - }; - } + function createBucketLister(entryProperty) { + return function(aggregatedArr) { + var startIndex = aggregatedArr.length; + for (var i = 0, entries = this.entries, len = entries.length; i < len; ++i) { + aggregatedArr[startIndex + i] = entries[i][entryProperty]; + } + }; + } - Bucket.prototype = { - getEqualityFunction: function(searchValue) { - return (typeof searchValue.equals == FUNCTION) ? equals_fixedValueHasEquals : equals_fixedValueNoEquals; - }, + Bucket.prototype = { + getEqualityFunction: function(searchValue) { + return (typeof searchValue.equals == FUNCTION) ? equals_fixedValueHasEquals : equals_fixedValueNoEquals; + }, - getEntryForKey: createBucketSearcher(ENTRY), + getEntryForKey: createBucketSearcher(ENTRY), - getEntryAndIndexForKey: createBucketSearcher(ENTRY_INDEX_AND_VALUE), + getEntryAndIndexForKey: createBucketSearcher(ENTRY_INDEX_AND_VALUE), - removeEntryForKey: function(key) { - var result = this.getEntryAndIndexForKey(key); - if (result) { - this.entries.splice(result[0], 1); - return result[1]; - } - return null; - }, + removeEntryForKey: function(key) { + var result = this.getEntryAndIndexForKey(key); + if (result) { + this.entries.splice(result[0], 1); + return result[1]; + } + return null; + }, - addEntry: function(key, value) { - this.entries.push( [key, value] ); - }, + addEntry: function(key, value) { + this.entries.push( [key, value] ); + }, - keys: createBucketLister(0), + keys: createBucketLister(0), - values: createBucketLister(1), + values: createBucketLister(1), - getEntries: function(destEntries) { - var startIndex = destEntries.length; - for (var i = 0, entries = this.entries, len = entries.length; i < len; ++i) { - // Clone the entry stored in the bucket before adding to array - destEntries[startIndex + i] = entries[i].slice(0); - } - }, + getEntries: function(destEntries) { + var startIndex = destEntries.length; + for (var i = 0, entries = this.entries, len = entries.length; i < len; ++i) { + // Clone the entry stored in the bucket before adding to array + destEntries[startIndex + i] = entries[i].slice(0); + } + }, - containsKey: createBucketSearcher(EXISTENCE), + containsKey: createBucketSearcher(EXISTENCE), - containsValue: function(value) { - var entries = this.entries, i = entries.length; - while (i--) { - if ( value === entries[i][1] ) { - return true; + containsValue: function(value) { + var entries = this.entries, i = entries.length; + while (i--) { + if ( value === entries[i][1] ) { + return true; + } } + return false; } - return false; - } - }; + }; - /*----------------------------------------------------------------------------------------------------------------*/ + /*----------------------------------------------------------------------------------------------------------------*/ - // Supporting functions for searching hashtable buckets + // Supporting functions for searching hashtable buckets - function searchBuckets(buckets, hash) { - var i = buckets.length, bucket; - while (i--) { - bucket = buckets[i]; - if (hash === bucket[0]) { - return i; + function searchBuckets(buckets, hash) { + var i = buckets.length, bucket; + while (i--) { + bucket = buckets[i]; + if (hash === bucket[0]) { + return i; + } } + return null; } - return null; - } - function getBucketForHash(bucketsByHash, hash) { - var bucket = bucketsByHash[hash]; + function getBucketForHash(bucketsByHash, hash) { + var bucket = bucketsByHash[hash]; - // Check that this is a genuine bucket and not something inherited from the bucketsByHash's prototype - return ( bucket && (bucket instanceof Bucket) ) ? bucket : null; - } + // Check that this is a genuine bucket and not something inherited from the bucketsByHash's prototype + return ( bucket && (bucket instanceof Bucket) ) ? bucket : null; + } - /*----------------------------------------------------------------------------------------------------------------*/ + /*----------------------------------------------------------------------------------------------------------------*/ - function Hashtable() { - var buckets = []; - var bucketsByHash = {}; - var properties = { - replaceDuplicateKey: true, - hashCode: hashObject, - equals: null - }; + function Hashtable() { + var buckets = []; + var bucketsByHash = {}; + var properties = { + replaceDuplicateKey: true, + hashCode: hashObject, + equals: null + }; - var arg0 = arguments[0], arg1 = arguments[1]; - if (arg1 !== UNDEFINED) { - properties.hashCode = arg0; - properties.equals = arg1; - } else if (arg0 !== UNDEFINED) { - merge(properties, arg0); - } + var arg0 = arguments[0], arg1 = arguments[1]; + if (arg1 !== UNDEFINED) { + properties.hashCode = arg0; + properties.equals = arg1; + } else if (arg0 !== UNDEFINED) { + merge(properties, arg0); + } - var hashCode = properties.hashCode, equals = properties.equals; - - this.properties = properties; - - this.put = function(key, value) { - checkKey(key); - checkValue(value); - var hash = hashCode(key), bucket, bucketEntry, oldValue = null; - - // Check if a bucket exists for the bucket key - bucket = getBucketForHash(bucketsByHash, hash); - if (bucket) { - // Check this bucket to see if it already contains this key - bucketEntry = bucket.getEntryForKey(key); - if (bucketEntry) { - // This bucket entry is the current mapping of key to value, so replace the old value. - // Also, we optionally replace the key so that the latest key is stored. - if (properties.replaceDuplicateKey) { - bucketEntry[0] = key; + var hashCode = properties.hashCode, equals = properties.equals; + + this.properties = properties; + + this.put = function(key, value) { + checkKey(key); + checkValue(value); + var hash = hashCode(key), bucket, bucketEntry, oldValue = null; + + // Check if a bucket exists for the bucket key + bucket = getBucketForHash(bucketsByHash, hash); + if (bucket) { + // Check this bucket to see if it already contains this key + bucketEntry = bucket.getEntryForKey(key); + if (bucketEntry) { + // This bucket entry is the current mapping of key to value, so replace the old value. + // Also, we optionally replace the key so that the latest key is stored. + if (properties.replaceDuplicateKey) { + bucketEntry[0] = key; + } + oldValue = bucketEntry[1]; + bucketEntry[1] = value; + } else { + // The bucket does not contain an entry for this key, so add one + bucket.addEntry(key, value); } - oldValue = bucketEntry[1]; - bucketEntry[1] = value; } else { - // The bucket does not contain an entry for this key, so add one - bucket.addEntry(key, value); + // No bucket exists for the key, so create one and put our key/value mapping in + bucket = new Bucket(hash, key, value, equals); + buckets.push(bucket); + bucketsByHash[hash] = bucket; } - } else { - // No bucket exists for the key, so create one and put our key/value mapping in - bucket = new Bucket(hash, key, value, equals); - buckets.push(bucket); - bucketsByHash[hash] = bucket; - } - return oldValue; - }; + return oldValue; + }; - this.get = function(key) { - checkKey(key); + this.get = function(key) { + checkKey(key); - var hash = hashCode(key); + var hash = hashCode(key); - // Check if a bucket exists for the bucket key - var bucket = getBucketForHash(bucketsByHash, hash); - if (bucket) { - // Check this bucket to see if it contains this key - var bucketEntry = bucket.getEntryForKey(key); - if (bucketEntry) { - // This bucket entry is the current mapping of key to value, so return the value. - return bucketEntry[1]; + // Check if a bucket exists for the bucket key + var bucket = getBucketForHash(bucketsByHash, hash); + if (bucket) { + // Check this bucket to see if it contains this key + var bucketEntry = bucket.getEntryForKey(key); + if (bucketEntry) { + // This bucket entry is the current mapping of key to value, so return the value. + return bucketEntry[1]; + } } - } - return null; - }; + return null; + }; - this.containsKey = function(key) { - checkKey(key); - var bucketKey = hashCode(key); + this.containsKey = function(key) { + checkKey(key); + var bucketKey = hashCode(key); - // Check if a bucket exists for the bucket key - var bucket = getBucketForHash(bucketsByHash, bucketKey); + // Check if a bucket exists for the bucket key + var bucket = getBucketForHash(bucketsByHash, bucketKey); - return bucket ? bucket.containsKey(key) : false; - }; + return bucket ? bucket.containsKey(key) : false; + }; - this.containsValue = function(value) { - checkValue(value); - var i = buckets.length; - while (i--) { - if (buckets[i].containsValue(value)) { - return true; + this.containsValue = function(value) { + checkValue(value); + var i = buckets.length; + while (i--) { + if (buckets[i].containsValue(value)) { + return true; + } } - } - return false; - }; + return false; + }; - this.clear = function() { - buckets.length = 0; - bucketsByHash = {}; - }; + this.clear = function() { + buckets.length = 0; + bucketsByHash = {}; + }; - this.isEmpty = function() { - return !buckets.length; - }; + this.isEmpty = function() { + return !buckets.length; + }; - var createBucketAggregator = function(bucketFuncName) { - return function() { - var aggregated = [], i = buckets.length; - while (i--) { - buckets[i][bucketFuncName](aggregated); - } - return aggregated; + var createBucketAggregator = function(bucketFuncName) { + return function() { + var aggregated = [], i = buckets.length; + while (i--) { + buckets[i][bucketFuncName](aggregated); + } + return aggregated; + }; }; - }; - this.keys = createBucketAggregator("keys"); - this.values = createBucketAggregator("values"); - this.entries = createBucketAggregator("getEntries"); + this.keys = createBucketAggregator("keys"); + this.values = createBucketAggregator("values"); + this.entries = createBucketAggregator("getEntries"); + + this.remove = function(key) { + checkKey(key); + + var hash = hashCode(key), bucketIndex, oldValue = null; + + // Check if a bucket exists for the bucket key + var bucket = getBucketForHash(bucketsByHash, hash); + + if (bucket) { + // Remove entry from this bucket for this key + oldValue = bucket.removeEntryForKey(key); + if (oldValue !== null) { + // Entry was removed, so check if bucket is empty + if (bucket.entries.length == 0) { + // Bucket is empty, so remove it from the bucket collections + bucketIndex = searchBuckets(buckets, hash); + buckets.splice(bucketIndex, 1); + delete bucketsByHash[hash]; + } + } + } + return oldValue; + }; - this.remove = function(key) { - checkKey(key); + this.size = function() { + var total = 0, i = buckets.length; + while (i--) { + total += buckets[i].entries.length; + } + return total; + }; + } - var hash = hashCode(key), bucketIndex, oldValue = null; + Hashtable.prototype = { + each: function(callback) { + var entries = this.entries(), i = entries.length, entry; + while (i--) { + entry = entries[i]; + callback(entry[0], entry[1]); + } + }, + + equals: function(hashtable) { + var keys, key, val, count = this.size(); + if (count == hashtable.size()) { + keys = this.keys(); + while (count--) { + key = keys[count]; + val = hashtable.get(key); + if (val === null || val !== this.get(key)) { + return false; + } + } + return true; + } + return false; + }, - // Check if a bucket exists for the bucket key - var bucket = getBucketForHash(bucketsByHash, hash); + putAll: function(hashtable, conflictCallback) { + var entries = hashtable.entries(); + var entry, key, value, thisValue, i = entries.length; + var hasConflictCallback = (typeof conflictCallback == FUNCTION); + while (i--) { + entry = entries[i]; + key = entry[0]; + value = entry[1]; - if (bucket) { - // Remove entry from this bucket for this key - oldValue = bucket.removeEntryForKey(key); - if (oldValue !== null) { - // Entry was removed, so check if bucket is empty - if (bucket.entries.length == 0) { - // Bucket is empty, so remove it from the bucket collections - bucketIndex = searchBuckets(buckets, hash); - buckets.splice(bucketIndex, 1); - delete bucketsByHash[hash]; + // Check for a conflict. The default behaviour is to overwrite the value for an existing key + if ( hasConflictCallback && (thisValue = this.get(key)) ) { + value = conflictCallback(key, thisValue, value); } + this.put(key, value); } - } - return oldValue; - }; + }, - this.size = function() { - var total = 0, i = buckets.length; - while (i--) { - total += buckets[i].entries.length; + clone: function() { + var clone = new Hashtable(this.properties); + clone.putAll(this); + return clone; } - return total; }; - } - Hashtable.prototype = { - each: function(callback) { + Hashtable.prototype.toQueryString = function() { var entries = this.entries(), i = entries.length, entry; + var parts = []; while (i--) { entry = entries[i]; - callback(entry[0], entry[1]); - } - }, - - equals: function(hashtable) { - var keys, key, val, count = this.size(); - if (count == hashtable.size()) { - keys = this.keys(); - while (count--) { - key = keys[count]; - val = hashtable.get(key); - if (val === null || val !== this.get(key)) { - return false; - } - } - return true; + parts[i] = encodeURIComponent( toStr(entry[0]) ) + "=" + encodeURIComponent( toStr(entry[1]) ); } - return false; - }, - - putAll: function(hashtable, conflictCallback) { - var entries = hashtable.entries(); - var entry, key, value, thisValue, i = entries.length; - var hasConflictCallback = (typeof conflictCallback == FUNCTION); - while (i--) { - entry = entries[i]; - key = entry[0]; - value = entry[1]; - - // Check for a conflict. The default behaviour is to overwrite the value for an existing key - if ( hasConflictCallback && (thisValue = this.get(key)) ) { - value = conflictCallback(key, thisValue, value); - } - this.put(key, value); - } - }, + return parts.join("&"); + }; - clone: function() { - var clone = new Hashtable(this.properties); - clone.putAll(this); - return clone; - } - }; - - Hashtable.prototype.toQueryString = function() { - var entries = this.entries(), i = entries.length, entry; - var parts = []; - while (i--) { - entry = entries[i]; - parts[i] = encodeURIComponent( toStr(entry[0]) ) + "=" + encodeURIComponent( toStr(entry[1]) ); - } - return parts.join("&"); - }; + return Hashtable; + })(); return Hashtable; -})(); \ No newline at end of file +} ) ); \ No newline at end of file