Path: blob/main/projects/HexGL/libs/Editor_files/jhashtable.js
4627 views
/**1* Copyright 2010 Tim Down.2*3* Licensed under the Apache License, Version 2.0 (the "License");4* you may not use this file except in compliance with the License.5* You may obtain a copy of the License at6*7* http://www.apache.org/licenses/LICENSE-2.08*9* Unless required by applicable law or agreed to in writing, software10* distributed under the License is distributed on an "AS IS" BASIS,11* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.12* See the License for the specific language governing permissions and13* limitations under the License.14*15* Author: Tim Down <[email protected]>16* Version: 2.117* Build date: 21 March 201018* Website: http://www.timdown.co.uk/jshashtable19*20* (Slight mod to add to gamecore namespace -- [email protected])21*/2223/**24* jshashtable25*26* jshashtable is a JavaScript implementation of a hash table. It creates a single constructor function called Hashtable27* in the global scope.28* Example:29* <code>30* var map = new gamecore.Hashtable();31* map.put('test1', obj);32* var obj = map.get('test1');33* </code>34*/3536gamecore.Hashtable = (function ()37{38var FUNCTION = "function";3940var arrayRemoveAt = (typeof Array.prototype.splice == FUNCTION) ?41function (arr, idx)42{43arr.splice(idx, 1);44} :4546function (arr, idx)47{48var itemsAfterDeleted, i, len;49if (idx === arr.length - 1)50{51arr.length = idx;52} else53{54itemsAfterDeleted = arr.slice(idx + 1);55arr.length = idx;56for (i = 0, len = itemsAfterDeleted.length; i < len; ++i)57{58arr[idx + i] = itemsAfterDeleted[i];59}60}61};6263function hashObject(obj)64{65var hashCode;66if (typeof obj == "string")67{68return obj;69} else if (typeof obj.hashCode == FUNCTION)70{71// Check the hashCode method really has returned a string72hashCode = obj.hashCode();73return (typeof hashCode == "string") ? hashCode : hashObject(hashCode);74} else if (typeof obj.toString == FUNCTION)75{76return obj.toString();77} else78{79try80{81return String(obj);82}83catch (ex)84{85// For host objects (such as ActiveObjects in IE) that have no toString() method and throw an error when86// passed to String()87return Object.prototype.toString.call(obj);88}89}90}9192function equals_fixedValueHasEquals(fixedValue, variableValue)93{94return fixedValue.equals(variableValue);95}9697function equals_fixedValueNoEquals(fixedValue, variableValue)98{99return (typeof variableValue.equals == FUNCTION) ?100variableValue.equals(fixedValue) : (fixedValue === variableValue);101}102103function createKeyValCheck(kvStr)104{105return function (kv)106{107if (kv === null)108{109throw new Error("null is not a valid " + kvStr);110} else if (typeof kv == "undefined")111{112throw new Error(kvStr + " must not be undefined");113}114};115}116117var checkKey = createKeyValCheck("key"), checkValue = createKeyValCheck("value");118119/*----------------------------------------------------------------------------------------------------------------*/120121function Bucket(hash, firstKey, firstValue, equalityFunction)122{123this[0] = hash;124this.entries = [];125this.addEntry(firstKey, firstValue);126127if (equalityFunction !== null)128{129this.getEqualityFunction = function ()130{131return equalityFunction;132};133}134}135136var EXISTENCE = 0, ENTRY = 1, ENTRY_INDEX_AND_VALUE = 2;137138function createBucketSearcher(mode)139{140return function (key)141{142var i = this.entries.length, entry, equals = this.getEqualityFunction(key);143while (i--)144{145entry = this.entries[i];146if (equals(key, entry[0]))147{148switch (mode)149{150case EXISTENCE:151return true;152case ENTRY:153return entry;154case ENTRY_INDEX_AND_VALUE:155return [ i, entry[1] ];156}157}158}159return false;160};161}162163function createBucketLister(entryProperty)164{165return function (aggregatedArr)166{167var startIndex = aggregatedArr.length;168for (var i = 0, len = this.entries.length; i < len; ++i)169{170aggregatedArr[startIndex + i] = this.entries[i][entryProperty];171}172};173}174175Bucket.prototype = {176getEqualityFunction:function (searchValue)177{178return (typeof searchValue.equals == FUNCTION) ? equals_fixedValueHasEquals : equals_fixedValueNoEquals;179},180181getEntryForKey:createBucketSearcher(ENTRY),182183getEntryAndIndexForKey:createBucketSearcher(ENTRY_INDEX_AND_VALUE),184185removeEntryForKey:function (key)186{187var result = this.getEntryAndIndexForKey(key);188if (result)189{190arrayRemoveAt(this.entries, result[0]);191return result[1];192}193return null;194},195196addEntry:function (key, value)197{198this.entries[this.entries.length] = [key, value];199},200201keys:createBucketLister(0),202203values:createBucketLister(1),204205getEntries:function (entries)206{207var startIndex = entries.length;208for (var i = 0, len = this.entries.length; i < len; ++i)209{210// Clone the entry stored in the bucket before adding to array211entries[startIndex + i] = this.entries[i].slice(0);212}213},214215containsKey:createBucketSearcher(EXISTENCE),216217containsValue:function (value)218{219var i = this.entries.length;220while (i--)221{222if (value === this.entries[i][1])223{224return true;225}226}227return false;228}229};230231/*----------------------------------------------------------------------------------------------------------------*/232233// Supporting functions for searching hashtable buckets234235function searchBuckets(buckets, hash)236{237var i = buckets.length, bucket;238while (i--)239{240bucket = buckets[i];241if (hash === bucket[0])242{243return i;244}245}246return null;247}248249function getBucketForHash(bucketsByHash, hash)250{251var bucket = bucketsByHash[hash];252253// Check that this is a genuine bucket and not something inherited from the bucketsByHash's prototype254return ( bucket && (bucket instanceof Bucket) ) ? bucket : null;255}256257/*----------------------------------------------------------------------------------------------------------------*/258259function Hashtable(hashingFunctionParam, equalityFunctionParam)260{261var that = this;262var buckets = [];263var bucketsByHash = {};264265var hashingFunction = (typeof hashingFunctionParam == FUNCTION) ? hashingFunctionParam : hashObject;266var equalityFunction = (typeof equalityFunctionParam == FUNCTION) ? equalityFunctionParam : null;267268this.put = function (key, value)269{270checkKey(key);271checkValue(value);272var hash = hashingFunction(key), bucket, bucketEntry, oldValue = null;273274// Check if a bucket exists for the bucket key275bucket = getBucketForHash(bucketsByHash, hash);276if (bucket)277{278// Check this bucket to see if it already contains this key279bucketEntry = bucket.getEntryForKey(key);280if (bucketEntry)281{282// This bucket entry is the current mapping of key to value, so replace old value and we're done.283oldValue = bucketEntry[1];284bucketEntry[1] = value;285} else286{287// The bucket does not contain an entry for this key, so add one288bucket.addEntry(key, value);289}290} else291{292// No bucket exists for the key, so create one and put our key/value mapping in293bucket = new Bucket(hash, key, value, equalityFunction);294buckets[buckets.length] = bucket;295bucketsByHash[hash] = bucket;296}297return oldValue;298};299300this.get = function (key)301{302checkKey(key);303304var hash = hashingFunction(key);305306// Check if a bucket exists for the bucket key307var bucket = getBucketForHash(bucketsByHash, hash);308if (bucket)309{310// Check this bucket to see if it contains this key311var bucketEntry = bucket.getEntryForKey(key);312if (bucketEntry)313{314// This bucket entry is the current mapping of key to value, so return the value.315return bucketEntry[1];316}317}318return null;319};320321this.containsKey = function (key)322{323checkKey(key);324var bucketKey = hashingFunction(key);325326// Check if a bucket exists for the bucket key327var bucket = getBucketForHash(bucketsByHash, bucketKey);328329return bucket ? bucket.containsKey(key) : false;330};331332this.containsValue = function (value)333{334checkValue(value);335var i = buckets.length;336while (i--)337{338if (buckets[i].containsValue(value))339{340return true;341}342}343return false;344};345346this.clear = function ()347{348buckets.length = 0;349bucketsByHash = {};350};351352this.isEmpty = function ()353{354return !buckets.length;355};356357var createBucketAggregator = function (bucketFuncName)358{359return function ()360{361var aggregated = [], i = buckets.length;362while (i--)363{364buckets[i][bucketFuncName](aggregated);365}366return aggregated;367};368};369370this.keys = createBucketAggregator("keys");371this.values = createBucketAggregator("values");372this.entries = createBucketAggregator("getEntries");373374this.remove = function (key)375{376checkKey(key);377378var hash = hashingFunction(key), bucketIndex, oldValue = null;379380// Check if a bucket exists for the bucket key381var bucket = getBucketForHash(bucketsByHash, hash);382383if (bucket)384{385// Remove entry from this bucket for this key386oldValue = bucket.removeEntryForKey(key);387if (oldValue !== null)388{389// Entry was removed, so check if bucket is empty390if (!bucket.entries.length)391{392// Bucket is empty, so remove it from the bucket collections393bucketIndex = searchBuckets(buckets, hash);394arrayRemoveAt(buckets, bucketIndex);395delete bucketsByHash[hash];396}397}398}399return oldValue;400};401402this.size = function ()403{404var total = 0, i = buckets.length;405while (i--)406{407total += buckets[i].entries.length;408}409return total;410};411412this.each = function (callback)413{414var entries = that.entries(), i = entries.length, entry;415while (i--)416{417entry = entries[i];418callback(entry[0], entry[1]);419}420};421422this.putAll = function (hashtable, conflictCallback)423{424var entries = hashtable.entries();425var entry, key, value, thisValue, i = entries.length;426var hasConflictCallback = (typeof conflictCallback == FUNCTION);427while (i--)428{429entry = entries[i];430key = entry[0];431value = entry[1];432433// Check for a conflict. The default behaviour is to overwrite the value for an existing key434if (hasConflictCallback && (thisValue = that.get(key)))435{436value = conflictCallback(key, thisValue, value);437}438that.put(key, value);439}440};441442this.clone = function ()443{444var clone = new Hashtable(hashingFunctionParam, equalityFunctionParam);445clone.putAll(that);446return clone;447};448449/**450* Added by [email protected] to support debug dumping of hash arrays451*/452this.toString = function ()453{454var result = '';455var keys = this.keys();456for (var i = 0; i < keys.length; i++)457{458var obj = this.get(keys[i]);459result += keys[i].toString() + ' = ' + obj.toString() + '\n';460}461462return result;463}464}465466return Hashtable;467})();468469