As made clear in update 3 on this answer, this notation:
var hash = {};
hash[X]
does not actually hash the object X
; it actually
JavaScript has no built-in general map type (sometimes called associative array or dictionary) which allows to access arbitrary values by arbitrary keys. JavaScript's fundamental data structure is the object, a special type of map which only accepts strings as keys and has special semantics like prototypical inheritance, getters and setters and some further voodoo.
When using objects as maps, you have to remember that the key will be converted to a string value via toString()
, which results in mapping 5
and '5'
to the same value and all objects which don't overwrite the toString()
method to the value indexed by '[object Object]'
. You might also involuntarily access its inherited properties if you don't check hasOwnProperty()
.
JavaScript's built-in array type does not help one bit: JavaScript arrays are not associative arrays, but just objects with a few more special properties. If you want to know why they can't be used as maps, look here.
Eugene Lazutkin already described the basic idea of using a custom hash function to generate unique strings which can be used to look up the associated values as properties of a dictionary object. This will most likely be the fastest solution, because objects are internally implemented as hash tables.
In order to get a unique hash value for arbitrary objects, one possibility is to use a global counter and cache the hash value in the object itself (for example, in a property named __hash
).
A hash function which does this is and works for both primitive values and objects is:
function hash(value) {
return (typeof value) + ' ' + (value instanceof Object ?
(value.__hash || (value.__hash = ++arguments.callee.current)) :
value.toString());
}
hash.current = 0;
This function can be used as described by Eugene. For convenience, we will further wrap it in a Map
class.
Map
implementationThe following implementation will additionally store the key-value-pairs in a doubly linked list in order to allow fast iteration over both keys and values. To supply your own hash function, you can overwrite the instance's hash()
method after creation.
// Linking the key-value-pairs is optional.
// If no argument is provided, linkItems === undefined, i.e. !== false
// --> linking will be enabled
function Map(linkItems) {
this.current = undefined;
this.size = 0;
if(linkItems === false)
this.disableLinking();
}
Map.noop = function() {
return this;
};
Map.illegal = function() {
throw new Error("illegal operation for maps without linking");
};
// Map initialisation from an existing object
// doesn't add inherited properties if not explicitly instructed to:
// omitting foreignKeys means foreignKeys === undefined, i.e. == false
// --> inherited properties won't be added
Map.from = function(obj, foreignKeys) {
var map = new Map;
for(var prop in obj) {
if(foreignKeys || obj.hasOwnProperty(prop))
map.put(prop, obj[prop]);
}
return map;
};
Map.prototype.disableLinking = function() {
this.link = Map.noop;
this.unlink = Map.noop;
this.disableLinking = Map.noop;
this.next = Map.illegal;
this.key = Map.illegal;
this.value = Map.illegal;
this.removeAll = Map.illegal;
return this;
};
// Overwrite in Map instance if necessary
Map.prototype.hash = function(value) {
return (typeof value) + ' ' + (value instanceof Object ?
(value.__hash || (value.__hash = ++arguments.callee.current)) :
value.toString());
};
Map.prototype.hash.current = 0;
// --- Mapping functions
Map.prototype.get = function(key) {
var item = this[this.hash(key)];
return item === undefined ? undefined : item.value;
};
Map.prototype.put = function(key, value) {
var hash = this.hash(key);
if(this[hash] === undefined) {
var item = { key : key, value : value };
this[hash] = item;
this.link(item);
++this.size;
}
else this[hash].value = value;
return this;
};
Map.prototype.remove = function(key) {
var hash = this.hash(key);
var item = this[hash];
if(item !== undefined) {
--this.size;
this.unlink(item);
delete this[hash];
}
return this;
};
// Only works if linked
Map.prototype.removeAll = function() {
while(this.size)
this.remove(this.key());
return this;
};
// --- Linked list helper functions
Map.prototype.link = function(item) {
if(this.size == 0) {
item.prev = item;
item.next = item;
this.current = item;
}
else {
item.prev = this.current.prev;
item.prev.next = item;
item.next = this.current;
this.current.prev = item;
}
};
Map.prototype.unlink = function(item) {
if(this.size == 0)
this.current = undefined;
else {
item.prev.next = item.next;
item.next.prev = item.prev;
if(item === this.current)
this.current = item.next;
}
};
// --- Iterator functions - only work if map is linked
Map.prototype.next = function() {
this.current = this.current.next;
};
Map.prototype.key = function() {
return this.current.key;
};
Map.prototype.value = function() {
return this.current.value;
};
The following script,
var map = new Map;
map.put('spam', 'eggs').
put('foo', 'bar').
put('foo', 'baz').
put({}, 'an object').
put({}, 'another object').
put(5, 'five').
put(5, 'five again').
put('5', 'another five');
for(var i = 0; i++ < map.size; map.next())
document.writeln(map.hash(map.key()) + ' : ' + map.value());
generates this output:
string spam : eggs
string foo : baz
object 1 : an object
object 2 : another object
number 5 : five again
string 5 : another five
PEZ suggested to overwrite the toString()
method, presumably with our hash function. This is not feasible, because it doesn't work for primitive values (changing toString()
for primitives is a very bad idea). If we want toString()
to return meaningful values for arbitrary objects, we would have to modify Object.prototype
, which some people (myself not included) consider verboten.
The current version of my Map
implementation as well as other JavaScript goodies can be obtained from here.
Here is an easy and convenient way of using something similar to the Java map:
var map = {
'map_name_1': map_value_1,
'map_name_2': map_value_2,
'map_name_3': map_value_3,
'map_name_4': map_value_4
}
And to get the value:
alert(map['map_name_1']); // Gives the value of map_value_1
... etc. ...
There are some really great solutions nowadays with external libraries:
JavaScript also has its language-provided Map
as well.
Yet another map implementation by me. With randomizer, 'generics' and 'iterator' =)
var HashMap = function (TKey, TValue) {
var db = [];
var keyType, valueType;
(function () {
keyType = TKey;
valueType = TValue;
})();
var getIndexOfKey = function (key) {
if (typeof key !== keyType)
throw new Error('Type of key should be ' + keyType);
for (var i = 0; i < db.length; i++) {
if (db[i][0] == key)
return i;
}
return -1;
}
this.add = function (key, value) {
if (typeof key !== keyType) {
throw new Error('Type of key should be ' + keyType);
} else if (typeof value !== valueType) {
throw new Error('Type of value should be ' + valueType);
}
var index = getIndexOfKey(key);
if (index === -1)
db.push([key, value]);
else
db[index][1] = value;
return this;
}
this.get = function (key) {
if (typeof key !== keyType || db.length === 0)
return null;
for (var i = 0; i < db.length; i++) {
if (db[i][0] == key)
return db[i][1];
}
return null;
}
this.size = function () {
return db.length;
}
this.keys = function () {
if (db.length === 0)
return [];
var result = [];
for (var i = 0; i < db.length; i++) {
result.push(db[i][0]);
}
return result;
}
this.values = function () {
if (db.length === 0)
return [];
var result = [];
for (var i = 0; i < db.length; i++) {
result.push(db[i][1]);
}
return result;
}
this.randomize = function () {
if (db.length === 0)
return this;
var currentIndex = db.length, temporaryValue, randomIndex;
while (0 !== currentIndex) {
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
temporaryValue = db[currentIndex];
db[currentIndex] = db[randomIndex];
db[randomIndex] = temporaryValue;
}
return this;
}
this.iterate = function (callback) {
if (db.length === 0)
return false;
for (var i = 0; i < db.length; i++) {
callback(db[i][0], db[i][1]);
}
return true;
}
}
Example:
var a = new HashMap("string", "number");
a.add('test', 1132)
.add('test14', 666)
.add('1421test14', 12312666)
.iterate(function (key, value) {console.log('a['+key+']='+value)});
/*
a[test]=1132
a[test14]=666
a[1421test14]=12312666
*/
a.randomize();
/*
a[1421test14]=12312666
a[test]=1132
a[test14]=666
*/
Adding yet another solution: HashMap
is pretty much the first class I ported from Java to JavaScript. You could say there is a lot of overhead, but the implementation is almost 100% equal to Java's implementation and includes all interfaces and subclasses.
The project can be found here: https://github.com/Airblader/jsava I'll also attach the (current) source code for the HashMap class, but as stated it also depends on the super class, etc. The OOP framework used is qooxdoo.
Please note that this code is already out-dated and refer to the GitHub project for the current work. As of writing this, there is also an ArrayList
implementation.
qx.Class.define( 'jsava.util.HashMap', {
extend: jsava.util.AbstractMap,
implement: [jsava.util.Map, jsava.io.Serializable, jsava.lang.Cloneable],
construct: function () {
var args = Array.prototype.slice.call( arguments ),
initialCapacity = this.self( arguments ).DEFAULT_INITIAL_CAPACITY,
loadFactor = this.self( arguments ).DEFAULT_LOAD_FACTOR;
switch( args.length ) {
case 1:
if( qx.Class.implementsInterface( args[0], jsava.util.Map ) ) {
initialCapacity = Math.max( ((args[0].size() / this.self( arguments ).DEFAULT_LOAD_FACTOR) | 0) + 1,
this.self( arguments ).DEFAULT_INITIAL_CAPACITY );
loadFactor = this.self( arguments ).DEFAULT_LOAD_FACTOR;
} else {
initialCapacity = args[0];
}
break;
case 2:
initialCapacity = args[0];
loadFactor = args[1];
break;
}
if( initialCapacity < 0 ) {
throw new jsava.lang.IllegalArgumentException( 'Illegal initial capacity: ' + initialCapacity );
}
if( initialCapacity > this.self( arguments ).MAXIMUM_CAPACITY ) {
initialCapacity = this.self( arguments ).MAXIMUM_CAPACITY;
}
if( loadFactor <= 0 || isNaN( loadFactor ) ) {
throw new jsava.lang.IllegalArgumentException( 'Illegal load factor: ' + loadFactor );
}
var capacity = 1;
while( capacity < initialCapacity ) {
capacity <<= 1;
}
this._loadFactor = loadFactor;
this._threshold = (capacity * loadFactor) | 0;
this._table = jsava.JsavaUtils.emptyArrayOfGivenSize( capacity, null );
this._init();
},
statics: {
serialVersionUID: 1,
DEFAULT_INITIAL_CAPACITY: 16,
MAXIMUM_CAPACITY: 1 << 30,
DEFAULT_LOAD_FACTOR: 0.75,
_hash: function (hash) {
hash ^= (hash >>> 20) ^ (hash >>> 12);
return hash ^ (hash >>> 7) ^ (hash >>> 4);
},
_indexFor: function (hashCode, length) {
return hashCode & (length - 1);
},
Entry: qx.Class.define( 'jsava.util.HashMap.Entry', {
extend: jsava.lang.Object,
implement: [jsava.util.Map.Entry],
construct: function (hash, key, value, nextEntry) {
this._value = value;
this._next = nextEntry;
this._key = key;
this._hash = hash;
},
members: {
_key: null,
_value: null,
/** @type jsava.util.HashMap.Entry */
_next: null,
/** @type Number */
_hash: 0,
getKey: function () {
return this._key;
},
getValue: function () {
return this._value;
},
setValue: function (newValue) {
var oldValue = this._value;
this._value = newValue;
return oldValue;
},
equals: function (obj) {
if( obj === null || !qx.Class.implementsInterface( obj, jsava.util.HashMap.Entry ) ) {
return false;
}
/** @type jsava.util.HashMap.Entry */
var entry = obj,
key1 = this.getKey(),
key2 = entry.getKey();
if( key1 === key2 || (key1 !== null && key1.equals( key2 )) ) {
var value1 = this.getValue(),
value2 = entry.getValue();
if( value1 === value2 || (value1 !== null && value1.equals( value2 )) ) {
return true;
}
}
return false;
},
hashCode: function () {
return (this._key === null ? 0 : this._key.hashCode()) ^
(this._value === null ? 0 : this._value.hashCode());
},
toString: function () {
return this.getKey() + '=' + this.getValue();
},
/**
* This method is invoked whenever the value in an entry is
* overwritten by an invocation of put(k,v) for a key k that's already
* in the HashMap.
*/
_recordAccess: function (map) {
},
/**
* This method is invoked whenever the entry is
* removed from the table.
*/
_recordRemoval: function (map) {
}
}
} )
},
members: {
/** @type jsava.util.HashMap.Entry[] */
_table: null,
/** @type Number */
_size: 0,
/** @type Number */
_threshold: 0,
/** @type Number */
_loadFactor: 0,
/** @type Number */
_modCount: 0,
/** @implements jsava.util.Set */
__entrySet: null,
/**
* Initialization hook for subclasses. This method is called
* in all constructors and pseudo-constructors (clone, readObject)
* after HashMap has been initialized but before any entries have
* been inserted. (In the absence of this method, readObject would
* require explicit knowledge of subclasses.)
*/
_init: function () {
},
size: function () {
return this._size;
},
isEmpty: function () {
return this._size === 0;
},
get: function (key) {
if( key === null ) {
return this.__getForNullKey();
}
var hash = this.self( arguments )._hash( key.hashCode() );
for( var entry = this._table[this.self( arguments )._indexFor( hash, this._table.length )];
entry !== null; entry = entry._next ) {
/** @type jsava.lang.Object */
var k;
if( entry._hash === hash && ((k = entry._key) === key || key.equals( k )) ) {
return entry._value;
}
}
return null;
},
__getForNullKey: function () {
for( var entry = this._table[0]; entry !== null; entry = entry._next ) {
if( entry._key === null ) {
return entry._value;
}
}
return null;
},
containsKey: function (key) {
return this._getEntry( key ) !== null;
},
_getEntry: function (key) {
var hash = (key === null) ? 0 : this.self( arguments )._hash( key.hashCode() );
for( var entry = this._table[this.self( arguments )._indexFor( hash, this._table.length )];
entry !== null; entry = entry._next ) {
/** @type jsava.lang.Object */
var k;
if( entry._hash === hash
&& ( ( k = entry._key ) === key || ( key !== null && key.equals( k ) ) ) ) {
return entry;
}
}
return null;
},
put: function (key, value) {
if( key === null ) {
return this.__putForNullKey( value );
}
var hash = this.self( arguments )._hash( key.hashCode() ),
i = this.self( arguments )._indexFor( hash, this._table.length );
for( var entry = this._table[i]; entry !== null; entry = entry._next ) {
/** @type jsava.lang.Object */
var k;
if( entry._hash === hash && ( (k = entry._key) === key || key.equals( k ) ) ) {
var oldValue = entry._value;
entry._value = value;
entry._recordAccess( this );
return oldValue;
}
}
this._modCount++;
this._addEntry( hash, key, value, i );
return null;
},
__putForNullKey: function (value) {
for( var entry = this._table[0]; entry !== null; entry = entry._next ) {
if( entry._key === null ) {
var oldValue = entry._value;
entry._value = value;
entry._recordAccess( this );
return oldValue;
}
}
this._modCount++;
this._addEntry( 0, null, value, 0 );
return null;
},
__putForCreate: function (key, value) {
var hash = (key === null) ? 0 : this.self( arguments )._hash( key.hashCode() ),
i = this.self( arguments )._indexFor( hash, this._table.length );
for( var entry = this._table[i]; entry !== null; entry = entry._next ) {
/** @type jsava.lang.Object */
var k;
if( entry._hash === hash
&& ( (k = entry._key) === key || ( key !== null && key.equals( k ) ) ) ) {
entry._value = value;
return;
}
}
this._createEntry( hash, key, value, i );
},
__putAllForCreate: function (map) {
var iterator = map.entrySet().iterator();
while( iterator.hasNext() ) {
var entry = iterator.next();
this.__putForCreate( entry.getKey(), entry.getValue() );
}
},
_resize: function (newCapacity) {
var oldTable = this._table,
oldCapacity = oldTable.length;
if( oldCapacity === this.self( arguments ).MAXIMUM_CAPACITY ) {
this._threshold = Number.MAX_VALUE;
return;
}
var newTable = jsava.JsavaUtils.emptyArrayOfGivenSize( newCapacity, null );
this._transfer( newTable );
this._table = newTable;
this._threshold = (newCapacity * this._loadFactor) | 0;
},
_transfer: function (newTable) {
var src = this._table,
newCapacity = newTable.length;
for( var j = 0; j < src.length; j++ ) {
var entry = src[j];
if( entry !== null ) {
src[j] = null;
do {
var next = entry._next,
i = this.self( arguments )._indexFor( entry._hash, newCapacity );
entry._next = newTable[i];
newTable[i] = entry;
entry = next;
} while( entry !== null );
}
}
},
putAll: function (map) {
var numKeyToBeAdded = map.size();
if( numKeyToBeAdded === 0 ) {
return;
}
if( numKeyToBeAdded > this._threshold ) {
var targetCapacity = (numKeyToBeAdded / this._loadFactor + 1) | 0;
if( targetCapacity > this.self( arguments ).MAXIMUM_CAPACITY ) {
targetCapacity = this.self( arguments ).MAXIMUM_CAPACITY;
}
var newCapacity = this._table.length;
while( newCapacity < targetCapacity ) {
newCapacity <<= 1;
}
if( newCapacity > this._table.length ) {
this._resize( newCapacity );
}
}
var iterator = map.entrySet().iterator();
while( iterator.hasNext() ) {
var entry = iterator.next();
this.put( entry.getKey(), entry.getValue() );
}
},
remove: function (key) {
var entry = this._removeEntryForKey( key );
return entry === null ? null : entry._value;
},
_removeEntryForKey: function (key) {
var hash = (key === null) ? 0 : this.self( arguments )._hash( key.hashCode() ),
i = this.self( arguments )._indexFor( hash, this._table.length ),
prev = this._table[i],
entry = prev;
while( entry !== null ) {
var next = entry._next,
/** @type jsava.lang.Object */
k;
if( entry._hash === hash
&& ( (k = entry._key) === key || ( key !== null && key.equals( k ) ) ) ) {
this._modCount++;
this._size--;
if( prev === entry ) {
this._table[i] = next;
} else {
prev._next = next;
}
entry._recordRemoval( this );
return entry;
}
prev = entry;
entry = next;
}
return entry;
},
_removeMapping: function (obj) {
if( obj === null || !qx.Class.implementsInterface( obj, jsava.util.Map.Entry ) ) {
return null;
}
/** @implements jsava.util.Map.Entry */
var entry = obj,
key = entry.getKey(),
hash = (key === null) ? 0 : this.self( arguments )._hash( key.hashCode() ),
i = this.self( arguments )._indexFor( hash, this._table.length ),
prev = this._table[i],
e = prev;
while( e !== null ) {
var next = e._next;
if( e._hash === hash && e.equals( entry ) ) {
this._modCount++;
this._size--;
if( prev === e ) {
this._table[i] = next;
} else {
prev._next = next;
}
e._recordRemoval( this );
return e;
}
prev = e;
e = next;
}
return e;
},
clear: function () {
this._modCount++;
var table = this._table;
for( var i = 0; i < table.length; i++ ) {
table[i] = null;
}
this._size = 0;
},
containsValue: function (value) {
if( value === null ) {
return this.__containsNullValue();
}
var table = this._table;
for( var i = 0; i < table.length; i++ ) {
for( var entry = table[i]; entry !== null; entry = entry._next ) {
if( value.equals( entry._value ) ) {
return true;
}
}
}
return false;
},
__containsNullValue: function () {
var table = this._table;
for( var i = 0; i < table.length; i++ ) {
for( var entry = table[i]; entry !== null; entry = entry._next ) {
if( entry._value === null ) {
return true;
}
}
}
return false;
},
clone: function () {
/** @type jsava.util.HashMap */
var result = null;
try {
result = this.base( arguments );
} catch( e ) {
if( !qx.Class.isSubClassOf( e.constructor, jsava.lang.CloneNotSupportedException ) ) {
throw e;
}
}
result._table = jsava.JsavaUtils.emptyArrayOfGivenSize( this._table.length, null );
result.__entrySet = null;
result._modCount = 0;
result._size = 0;
result._init();
result.__putAllForCreate( this );
return result;
},
_addEntry: function (hash, key, value, bucketIndex) {
var entry = this._table[bucketIndex];
this._table[bucketIndex] = new (this.self( arguments ).Entry)( hash, key, value, entry );
if( this._size++ >= this._threshold ) {
this._resize( 2 * this._table.length );
}
},
_createEntry: function (hash, key, value, bucketIndex) {
var entry = this._table[bucketIndex];
this._table[bucketIndex] = new (this.self( arguments ).Entry)( hash, key, value, entry );
this._size++;
},
keySet: function () {
var keySet = this._keySet;
return keySet !== null ? keySet : ( this._keySet = new this.KeySet( this ) );
},
values: function () {
var values = this._values;
return values !== null ? values : ( this._values = new this.Values( this ) );
},
entrySet: function () {
return this.__entrySet0();
},
__entrySet0: function () {
var entrySet = this.__entrySet;
return entrySet !== null ? entrySet : ( this.__entrySet = new this.EntrySet( this ) );
},
/** @private */
HashIterator: qx.Class.define( 'jsava.util.HashMap.HashIterator', {
extend: jsava.lang.Object,
implement: [jsava.util.Iterator],
type: 'abstract',
/** @protected */
construct: function (thisHashMap) {
this.__thisHashMap = thisHashMap;
this._expectedModCount = this.__thisHashMap._modCount;
if( this.__thisHashMap._size > 0 ) {
var table = this.__thisHashMap._table;
while( this._index < table.length && ( this._next = table[this._index++] ) === null ) {
// do nothing
}
}
},
members: {
__thisHashMap: null,
/** @type jsava.util.HashMap.Entry */
_next: null,
/** @type Number */
_expectedModCount: 0,
/** @type Number */
_index: 0,
/** @type jsava.util.HashMap.Entry */
_current: null,
hasNext: function () {
return this._next !== null;
},
_nextEntry: function () {
if( this.__thisHashMap._modCount !== this._expectedModCount ) {
throw new jsava.lang.ConcurrentModificationException();
}
var entry = this._next;
if( entry === null ) {
throw new jsava.lang.NoSuchElementException();
}
if( (this._next = entry._next) === null ) {
var table = this.__thisHashMap._table;
while( this._index < table.length && ( this._next = table[this._index++] ) === null ) {
// do nothing
}
}
this._current = entry;
return entry;
},
remove: function () {
if( this._current === null ) {
throw new jsava.lang.IllegalStateException();
}
if( this.__thisHashMap._modCount !== this._expectedModCount ) {
throw new jsava.lang.ConcurrentModificationException();
}
var key = this._current._key;
this._current = null;
this.__thisHashMap._removeEntryForKey( key );
this._expectedModCount = this.__thisHashMap._modCount;
}
}
} ),
_newKeyIterator: function () {
return new this.KeyIterator( this );
},
_newValueIterator: function () {
return new this.ValueIterator( this );
},
_newEntryIterator: function () {
return new this.EntryIterator( this );
},
/** @private */
ValueIterator: qx.Class.define( 'jsava.util.HashMap.ValueIterator', {
extend: jsava.util.HashMap.HashIterator,
construct: function (thisHashMap) {
this.base( arguments, thisHashMap );
},
members: {
next: function () {
return this._nextEntry()._value;
}
}
} ),
/** @private */
KeyIterator: qx.Class.define( 'jsava.util.HashMap.KeyIterator', {
extend: jsava.util.HashMap.HashIterator,
construct: function (thisHashMap) {
this.base( arguments, thisHashMap );
},
members: {
next: function () {
return this._nextEntry().getKey();
}
}
} ),
/** @private */
EntryIterator: qx.Class.define( 'jsava.util.HashMap.EntryIterator', {
extend: jsava.util.HashMap.HashIterator,
construct: function (thisHashMap) {
this.base( arguments, thisHashMap );
},
members: {
next: function () {
return this._nextEntry();
}
}
} ),
/** @private */
KeySet: qx.Class.define( 'jsava.util.HashMap.KeySet', {
extend: jsava.util.AbstractSet,
construct: function (thisHashMap) {
this.base( arguments );
this.__thisHashMap = thisHashMap;
},
members: {
__thisHashMap: null,
iterator: function () {
return this.__thisHashMap._newKeyIterator();
},
size: function () {
return this.__thisHashMap._size;
},
contains: function (obj) {
return this.__thisHashMap.containsKey( obj );
},
remove: function (obj) {
return this.__thisHashMap._removeEntryForKey( obj ) !== null;
},
clear: function () {
this.__thisHashMap.clear();
}
}
} ),
/** @private */
Values: qx.Class.define( 'jsava.util.HashMap.Values', {
extend: jsava.util.AbstractCollection,
construct: function (thisHashMap) {
this.base( arguments );
this.__thisHashMap = thisHashMap;
},
members: {
__thisHashMap: null,
iterator: function () {
return this.__thisHashMap._newValueIterator();
},
size: function () {
return this.__thisHashMap._size;
},
contains: function (obj) {
return this.__thisHashMap.containsValue( obj );
},
clear: function () {
this.__thisHashMap.clear();
}
}
} ),
/** @private */
EntrySet: qx.Class.define( 'jsava.util.HashMap.EntrySet', {
extend: jsava.util.AbstractSet,
construct: function (thisHashMap) {
this.base( arguments );
this.__thisHashMap = thisHashMap;
},
members: {
__thisHashMap: null,
iterator: function () {
return this.__thisHashMap._newEntryIterator();
},
size: function () {
return this.__thisHashMap._size;
},
contains: function (obj) {
if( obj === null || !qx.Class.implementsInterface( obj, jsava.util.Map.Entry ) ) {
return false;
}
/** @implements jsava.util.Map.Entry */
var entry = obj,
candidate = this.__thisHashMap._getEntry( entry.getKey() );
return candidate !== null && candidate.equals( entry );
},
remove: function (obj) {
return this.__thisHashMap._removeMapping( obj ) !== null;
},
clear: function () {
this.__thisHashMap.clear();
}
}
} )
}
} );
I've implemented a JavaScript HashMap which code can be obtained from http://github.com/lambder/HashMapJS/tree/master
Here is the code:
/*
=====================================================================
@license MIT
@author Lambder
@copyright 2009 Lambder.
@end
=====================================================================
*/
var HashMap = function() {
this.initialize();
}
HashMap.prototype = {
hashkey_prefix: "<#HashMapHashkeyPerfix>",
hashcode_field: "<#HashMapHashkeyPerfix>",
initialize: function() {
this.backing_hash = {};
this.code = 0;
},
/*
Maps value to key returning previous association
*/
put: function(key, value) {
var prev;
if (key && value) {
var hashCode = key[this.hashcode_field];
if (hashCode) {
prev = this.backing_hash[hashCode];
} else {
this.code += 1;
hashCode = this.hashkey_prefix + this.code;
key[this.hashcode_field] = hashCode;
}
this.backing_hash[hashCode] = value;
}
return prev;
},
/*
Returns value associated with given key
*/
get: function(key) {
var value;
if (key) {
var hashCode = key[this.hashcode_field];
if (hashCode) {
value = this.backing_hash[hashCode];
}
}
return value;
},
/*
Deletes association by given key.
Returns true if the association existed, false otherwise
*/
del: function(key) {
var success = false;
if (key) {
var hashCode = key[this.hashcode_field];
if (hashCode) {
var prev = this.backing_hash[hashCode];
this.backing_hash[hashCode] = undefined;
if(prev !== undefined)
success = true;
}
}
return success;
}
}
//// Usage
// Creation
var my_map = new HashMap();
// Insertion
var a_key = {};
var a_value = {struct: "structA"};
var b_key = {};
var b_value = {struct: "structB"};
var c_key = {};
var c_value = {struct: "structC"};
my_map.put(a_key, a_value);
my_map.put(b_key, b_value);
var prev_b = my_map.put(b_key, c_value);
// Retrieval
if(my_map.get(a_key) !== a_value){
throw("fail1")
}
if(my_map.get(b_key) !== c_value){
throw("fail2")
}
if(prev_b !== b_value){
throw("fail3")
}
// Deletion
var a_existed = my_map.del(a_key);
var c_existed = my_map.del(c_key);
var a2_existed = my_map.del(a_key);
if(a_existed !== true){
throw("fail4")
}
if(c_existed !== false){
throw("fail5")
}
if(a2_existed !== false){
throw("fail6")
}