Javascript HashTable use Object key
Solution 1
Here is a proposal:
function HashTable() {
this.hashes = {};
}
HashTable.prototype = {
constructor: HashTable,
put: function( key, value ) {
this.hashes[ JSON.stringify( key ) ] = value;
},
get: function( key ) {
return this.hashes[ JSON.stringify( key ) ];
}
};
The API is exactly as shown in your question.
You can't play with the reference in js however (so two empty objects will look like the same to the hashtable), because you have no way to get it. See this answer for more details: How to get javascript object references or reference count?
Jsfiddle demo: http://jsfiddle.net/HKz3e/
However, for the unique side of things, you could play with the original objects, like in this way:
function HashTable() {
this.hashes = {},
this.id = 0;
}
HashTable.prototype = {
constructor: HashTable,
put: function( obj, value ) {
obj.id = this.id;
this.hashes[ this.id ] = value;
this.id++;
},
get: function( obj ) {
return this.hashes[ obj.id ];
}
};
Jsfiddle demo: http://jsfiddle.net/HKz3e/2/
This means that your objects need to have a property named id
that you won't use elsewhere. If you want to have this property as non-enumerable, I suggest you take a look at defineProperty
(it's not cross-browser however, even with ES5-Shim, it doesn't work in IE7).
It also means you are limited on the number of items you can store in this hashtable. Limited to 253, that is.
And now, the "it's not going to work anywhere" solution: use ES6 WeakMaps. They are done exactly for this purpose: having objects as keys. I suggest you read MDN for more information: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/WeakMap
It slightly differs from your API though (it's set
and not put
):
var myMap = new WeakMap(),
object1 = {},
object2 = {};
myMap.set( object1, 'value1' );
myMap.set( object2, 'value2' );
console.log( myMap.get( object1 ) ); // "value1"
console.log( myMap.get( object2 ) ); // "value2"
Jsfiddle demo with a weakmap shim: http://jsfiddle.net/Ralt/HKz3e/9/
However, weakmaps are implemented in FF and Chrome (only if you enable the "Experimental javascript features" flag in chrome however). There are shims available, like this one: https://gist.github.com/1269991. Use at your own risk.
You can also use Maps
, they may more suit your needs, since you also need to store primitive values (strings) as keys. Doc, Shim.
Solution 2
Here is a simple Map
implementation that will work with any type of key, including object references, and it will not mutate the key in any way:
function Map() {
var keys = [], values = [];
return {
put: function (key, value) {
var index = keys.indexOf(key);
if(index == -1) {
keys.push(key);
values.push(value);
}
else {
values[index] = value;
}
},
get: function (key) {
return values[keys.indexOf(key)];
}
};
}
While this yields the same functionality as a hash table, it's not actually implemented using a hash function since it iterates over arrays and has a worst case performance of O(n). However, for the vast majority of sensible use cases this shouldn't be a problem at all. The indexOf
function is implemented by the JavaScript engine and is highly optimized.
Solution 3
I took @Florian Margaine's suggestion to higher level and came up with this:
function HashTable(){
var hash = new Object();
this.put = function(key, value){
if(typeof key === "string"){
hash[key] = value;
}
else{
if(key._hashtableUniqueId == undefined){
key._hashtableUniqueId = UniqueId.prototype.generateId();
}
hash[key._hashtableUniqueId] = value;
}
};
this.get = function(key){
if(typeof key === "string"){
return hash[key];
}
if(key._hashtableUniqueId == undefined){
return undefined;
}
return hash[key._hashtableUniqueId];
};
}
function UniqueId(){
}
UniqueId.prototype._id = 0;
UniqueId.prototype.generateId = function(){
return (++UniqueId.prototype._id).toString();
};
Usage
var map = new HashTable();
var object1 = new Object();
map.put(object1, "Cocakola");
alert(map.get(object1)); // Cocakola
//Overriding
map.put(object1, "Cocakola 2");
alert(map.get(object1)); // Cocakola 2
// String key is used as String
map.put("myKey", "MyValue");
alert(map.get("myKey")); // MyValue
alert(map.get("my".concat("Key"))); // MyValue
// Invalid keys
alert(map.get("unknownKey")); // undefined
alert(map.get(new Object())); // undefined
Solution 4
Here is a proposal, combining @Florian's solution with @Laurent's.
function HashTable() {
this.hashes = [];
}
HashTable.prototype = {
constructor: HashTable,
put: function( key, value ) {
this.hashes.push({
key: key,
value: value
});
},
get: function( key ) {
for( var i = 0; i < this.hashes.length; i++ ){
if(this.hashes[i].key == key){
return this.hashes[i].value;
}
}
}
};
It wont change your object in any way and it doesn't rely on JSON.stringify.
Solution 5
When you say you don't want your Object keys converted into Strings, I'm going to assume it's because you just don't want the entire code contents of your Objects being used as keys. This, of course, makes perfect sense.
While there is no "hash table" in Javascript per-se, you can accomplish what you're looking for by simply overriding your Object's prototype.toString and returning a valid key value that will be unique to each instance. One way to do this is with Symbol():
function Obj () {
this.symbol = Symbol() // Guaranteed to be unique to each instance
}
Obj.prototype.toString = function () {
return this.symbol // Return the unique Symbol, instead of Obj's stringified code
}
let a = new Obj()
let b = new Obj()
let table = {}
table[a] = 'A'
table[b] = 'B'
console.log(table) // {Symbol(): 'A', Symbol(): 'B'}
console.log(table[a]) // A
console.log(table[b]) // B
Related videos on Youtube
![Ilya Gazman](https://i.stack.imgur.com/KcrPN.jpg?s=256&g=1)
Ilya Gazman
My latest app Facetocall it had a site too: facetocall.com Developed with my latest SDK
Updated on July 09, 2022Comments
-
Ilya Gazman almost 2 years
I want to create a hash table with
Object
keys that are not converted into String.Some thing like this:
var object1 = new Object(); var object2 = new Object(); var myHash = new HashTable(); myHash.put(object1, "value1"); myHash.put(object2, "value2"); alert(myHash.get(object1), myHash.get(object2)); // I wish that it will print value1 value2
EDIT: See my answer for full solution
-
Anna B about 9 yearsIn ES6, you can use WeakMap for this purpose.
-
neaumusic almost 8 years
-
Koray Tugay almost 3 yearsBoth links above are dead, recent one: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
-
-
Ilya Gazman about 12 yearsThis is not hashtable, this is bad practis of array list with efficiency of O(n). It's not what I am looking
-
laurent about 12 yearsThere is no built-in hash table in JavaScript so, if you don't want to convert the object to a string, you need to use an array and loop through it. How much data will your table store?
-
Ilya Gazman about 12 yearsthousandths,I need to find a solution some how. Converting to string not promise me a unique key.
-
Ilya Gazman about 12 yearsThis is a solution indeed, I am just wondering how heavy is that JSON.stringify
-
laurent about 12 years@Babibu, it depends on the size of the object. The bigger it is, the longer it will take to stringify it. If storing objects in a simple array is overkill for you, then stringifying your thousands of objects will probably be too.
-
Florian Margaine about 12 years
JSON.stringify
is cheap. Really. This is not going to be the bottleneck of your application. -
Florian Margaine about 12 yearsBut beware that it doesn't exactly fill in your original needs: a unique key for every object. As said in the answer, you can't do that.
-
Florian Margaine about 12 years@Babibu I edited to add a solution to work around this, and it's "cheaper" than
JSON.stringify
, although it has a little drawback: you need a special property. -
Ilya Gazman about 12 yearsYeah I think it will do the job. The only problem with your solution is that you manage the id inside HashTable witch is wrong. It need to be in some place that is unique to the application, so I be able to have more then just one HashTable
-
Florian Margaine about 12 yearsWhy? Each HashTable has unique object/values in itself, what's wrong about it?
-
laurent about 12 years@Babidu, you write "The only problem with your solution is that you manage the id inside HashTable". So why don't you manage it outside the class? Just define some kind of global variable. It seems you are looking for someone to write a ready-made solution for you. Florian Margaine provided a good start which just needs to be tweaked for your need.
-
Ilya Gazman about 12 years@FlorianMargaine After working a bit I posted a complete solution
-
Florian Margaine about 12 yearsGlad you got it sorted out :)
-
Florian Margaine about 12 years@Babibu just for the sake of completeness, I added a last method, the one that should be used, using WeakMaps. They're not supported everywhere however.
-
Peter V. Mørch about 11 yearsThis is wrong. You cannot rely on the ordering of keys in JSON.stringify(). To different objects with the same keys and values are not guaranteed to return the same string with JSON.stringify() (even though they do "most of the time"). See javascript - Is there a deterministic equivalent of JSON.stringify? for a better (but not-trivial-JSON.stringify()) string for an object.
-
Florian Margaine about 11 years@Peter in reality, JSON.stringify is deterministic on the same implemebtation, as the answer you linked to says. If you really want deterministic order across implementations (which is not needed there), it's trivial to implement.
-
Peter V. Mørch almost 11 years@FlorianMargaine I didn't read anywhere in that question that it should be deterministic on the same implementation. That seems to be the case, though. I did however read that MDN's stringify page says "Properties of non-array objects are not guaranteed to be stringified in any particular order. Do not rely on ordering of properties within the same object within the stringification." Even though it works today, there are no guarantees for tomorrow.
-
stamat almost 11 yearsAs Peter claimed objects in JavaScript are unordered, that is true. But I guess 'for var i in obj' might not always return the correct order of properties as they were put in object. And I guess stringify uses the same iteration. So why not make a recursive function that reorders the properties alphabetically. And then use stringify?, or if it is not reliable make your own function that writes json!? I've thought about this a lot... And I've tried a similar solution, check it out: stamat.wordpress.com/…
-
sethro over 10 yearsThe problem with this, is it is not a "Hash" table; there is no hashing going on. You just created a misleading wrapper for an array. Also, because of your use of "==", you will get unexpected results when using mixed types as keys informit.com/articles/article.aspx?p=1997934&seqNum=5.
-
sethro over 10 yearsThis requires that you always keep a reference to the object you used as a key, when calling
put()
. If you are going to do that, why map the values in the first place; why not just keep references to the values instead? Have you considered what will happen if you have two objects that are equivalent, but not the same object (!==) ? They should hash to the same value, but theget()
method will fail you for any valid key that is not the exact object used input()
since you only added the._hashtableUniqueId
property to one of them. -
sethro over 10 yearsthe
.id
addition creates a meaningless hashtable. It requires you to always keep a reference to the exact object you were using as a key when you calledput()
. The purpose of the map is defeated; if you always have a reference to the same object, you should never have to "look it up". You'd be better of keeping a reference to the value you are trying to map. You need the functionality that allows two objects, that are not the same object in memory, to be equivalent. Because that is missing,get()
will fail for any meaningful use case. I hope that makes sense. -
edrian almost 8 yearsNice solution!! I've improved this solution by adding _hashtableUniqueId to key using Object.defineProperty and configuring it as not enumerable, so it won't appear in, for example, json requests. Also UniqueID object is not necessary. You can look at my answer for details.
-
Ilya Gazman almost 8 yearsWhat if I map one item in two different HashTable maps? You will get duplicated ids... To avoid this I used the UniqueId object. Good one with jsons.
-
edrian almost 8 years@Ilya_Gazman thanks for noticing that!, fixed the code above. Didn't have that problem in my AngularJS implementation because there was an extra closure in factory method.
-
Ilya Gazman over 7 yearsNo @ShawnMoore it will be fine. you will get "101"._hashtableUniqueId equals to some random number
-
Sasha Chedygov over 6 yearsTo future readers: Note that
Map
is no longer a good name for this since it conflicts with the built-inMap
type. -
rebelzach over 6 yearsIts 2018 and WeakMaps have much better support now.
-
Florian Margaine over 6 yearsIndeed, much better than in 2012 :)
-
Peter over 5 yearsI used a closure to enforce encapsulation—we are creating an abstraction. The underlying data structure should only be mutable using our explicit public interface. This reduces the likelihood of bugs/errors/misuse in application code. We can still debug the internals in the debugger, or add additional convenience methods such as
hasKey()
orsize()
. -
kungfooman over 5 years@Peter it just obfuscates understanding by now allowing new developers to actually understand the internals via F12/devtools. You only think you do something good, but actually it prevents understanding and hence THAT leads to bugs.
-
Peter over 5 yearsThe whole idea behind abstraction is that it allows us to reason about the problem we are trying to solve at a higher level. The
Map
defines a contract and allows us to offload a certain amount of mental load. Think of this like any other JS engine builtin. Software systems are all about building abstractions upon abstractions until we can express in terms of “what” we want instead of “how”. This really is the only thing that prevents large projects (with hundreds of contributors) from collapsing. -
kungfooman over 5 yearsOh really? I work with a large WebGL/PBR/Game engine on GitHub and it exposes everything for debugging purposes. And guess what, it's doing great, far from collapsing. Just don't merge shitty Pull Requests until they fixed the code...
-
Peter over 5 yearsSorry, didn’t mean to get personal. You are right—things like core libraries, game engines, kernels, device drivers, etc. all make these kind of trade offs—and for good reason—to maximize performance. This is possible with a highly skilled and disciplined team. What I am talking about is risk mitigation, when you can.
-
Nirvana about 3 yearsif you to define variable without
var
keyword, it will not work in strict mode -
Nirvana about 3 yearsWhy do objects need to implement "hashString()" method themselves? couldn't you just define one yourself? see this: how-to-reliably-hash-javascript-objects
-
Qtax about 2 yearsIs this better than
WeakMap
somehow?