I need a true iterator that will work like this:
var haystackObj = {
'needle': 'abc',
'prop2': {
'prop1': 'def',
'prop2': {
'needle': 'ghi',
},
'needle': 'jkl',
},
};
var needleKey = 'needle';
var iterator = {
next: function () {
/*
* WHAT CODE GOES HERE?
*
* Should return the next property, recursively, with the name
* equal to needleKey, of haystackObj.
*
*/
}
};
var value = iterator.next();
console.log(value); // -> 'abc'
value = iterator.next();
console.log(value); // -> 'ghi'
value = iterator.next();
console.log(value); // -> 'jkl'
I think this would be trivial with a for(k in o) and first-class continuations, but JS doesn't have those.
EDIT: I can only scan haystackObj once.
EDIT2: I am not looking for "a way to iterate through object properties." I am looking for an iterator of object properties. That is a huge difference. The problem is not as trivial as it may look at first glance.
Properties order is not guaranteed in JS. Different engines behave differently. (Some engines based on alphabetical order, other based on last added order.)
Your requirements are thus impossible to fulfill.
If you just wanted an iterator without minding the order, you could take a look at this question/answers: How to simulate JavaScript yield?
This is what the spec says about the properties order:
The mechanics and order of enumerating the properties (step 6.a in the first algorithm, step 7.a in the second) is not specified. Properties of the object being enumerated may be deleted during enumeration. If a property that has not yet been visited during enumeration is deleted, then it will not be visited. If new properties are added to the object being enumerated during enumeration, the newly added properties are not guaranteed to be visited in the active enumeration. A property name must not be visited more than once in any enumeration.
In reality however, you can expect a certain order from most browsers: Elements order in a "for (… in …)" loop
The only way I see to implement a fake generator (according to the fact that the order in reality suits you) would be to copy your object, and delete the scanned properties of the copy when needed. This'd mean you wouldn't rescan twice the same properties. Some code example:
var Iterator = function() {
var copy = $.extend(haystackObj, true);
// ^ using jQuery's extend for a quick function, but use w/e you want.
// Anyway keep it in a closure. This copy will have its properties deleted
// after each iteration.
return {
next: function next() {
var found = false,
needle;
for (var prop in copy) {
if (typeof copy[prop] === 'object') {
// Since next() doesn't take any argument...
// That's a bad solution. You should use an inner function
// to recurse. But I'm going to bed right now!
var copyCopy = $.extend(copy, true);
copy = copy[prop];
found = next();
copy = copyCopy;
}
else {
if (prop === needleKey) {
found = true;
}
}
if (found) {
needle = copy[prop];
}
// Delete the current property to simulate a real generator.
delete copy[prop];
if (found) {
return needle;
}
}
}
};
};
// Usage:
var iterator = Iterator();
iterator.next(); // "abc"
This code doesn't work (see jsfiddle), and I'm going to sleep. But you can see where it's going and how you could make something.
Assuming I understand you correctly, and bearing in mind that this is not a 'true yield', and putting all the code where you seem to want it,
var iterator = {
next: function () {
/*
* WHAT CODE GOES HERE?
*
* Should return the next property, recursively, with the name
* equal to needleKey, of haystackObj.
*
*/
var values=[], findneedles;
findneedles = function(o){
var k;
for(k in o){
if(k === needleKey){
values.push(o[k]);
}else if(typeof o[k] === 'object'){
findneedles(o[k]);
}
}
};
findneedles(haystackObj);
this.next = function(){
return values.shift();
};
return values.shift();
}
};
Although Florian Margaine's answer points out that the order of the properties are dependent on the js engine, this solution works in chrome. Took me a little bit of tweaking, but here it is http://jsfiddle.net/6zCkJ/3/:
Edited (this solution was done before OP said the tree can only be processed once)
var needleKey = 'needle';
var currIndex = 0;
var runningIndex = 0;
var getValueByIndex = function (obj) {
var objToSearch = obj || haystackObj;
for (var x in objToSearch) {
if (x == needleKey) {
if (runningIndex == currIndex) {
currIndex += 1;
return objToSearch[x];
}
runningIndex += 1;
} else if (typeof objToSearch[x] == 'object') {
var found = getValueByIndex(objToSearch[x]);
if (found) return found;
}
}
}
var iterator = {
next: function () {
runningIndex = 0;
return getValueByIndex(0);
}
};
Another approach which will only traverse the tree a single time is as follows http://jsfiddle.net/6zCkJ/6/. The catch is that you must load the values array whenever the needle is updated:
var currIndex = 0;
var valuesArray = [];
var loadValues = function (obj) {
var objToSearch = obj || haystackObj;
for (var x in objToSearch) {
if (x == needleKey) {
valuesArray.push(objToSearch[x])
} else if (typeof objToSearch[x] == 'object') {
loadValues(objToSearch[x]);
}
}
}
loadValues();
console.log(valuesArray);
var iterator = {
next: function () {
return valuesArray[currIndex++];
}
};
Edit: So far all answers posted here involve having to navigate the whole tree at least once or more which is not what the OP is looking for, including having to copy the object and remove properties as they are traversed. There is a solution though which involves marking the objects as they traversed with meta data which allows you to skip over the objects the next time they are encountered. Using my first approach it would be rather trivial to add these optimizations and hopefully accomplish what the OP is requesting.
Alright, so I couldn't resist trying to get this to work. Here is how I did it http://jsfiddle.net/6zCkJ/12/ . You can see that I am storing the found objects in the foundObjects object, where the key is made up of the path to that object so you can do a quick lookup to see if it has already been recursed over. The numFound is used to increment the running index properly. I have not tested this heavily, but it should be a good start:
var Iterator = function () {
var needleKey = 'needle';
var currIndex = 0;
var runningIndex = 0;
var foundObjects = {};
var getValueByIndex = function (obj,currentPath) {
var objToSearch = obj || haystackObj;
for (var x in objToSearch) {
currentPath += x + '_';
if (x == needleKey) {
if (runningIndex == currIndex) {
currIndex += 1;
if (!foundObjects[currentPath]) {
foundObjects[currentPath] = {
numFound: 0,
finished: false
};
}
foundObjects[currentPath].numFound += 1;
return objToSearch[x];
}
runningIndex += 1;
} else if (typeof objToSearch[x] == 'object') {
if (foundObjects[currentPath] && foundObjects[currentPath].finished) {
runningIndex += foundObjects[currentPath].numFound;
} else {
var found = getValueByIndex(objToSearch[x],currentPath);
if (found) {
return found;
}
}
}
if (!foundObjects[currentPath]) {
foundObjects[currentPath] = {
numFound: 0,
finished: true
};
}
foundObjects[currentPath].finished = true;
}
}
this.next = function () {
runningIndex = 0;
return getValueByIndex(0,'');
}
};
var iterator = new Iterator();
var value = iterator.next();
I'm going to answer this one for posterity.
In ECMAScript 6 we have a yield statement. But lets say you're nuts and you'd like to use this feature right now. Compiled using the traceur-compiler to your plain old JavaScript, we get the following.
Input:
var iterator = function* (object) {
for(var key in object) {
yield key;
for( k of iterator(object[key]) ) {
yield k;
}
}
};
var o = {
a: 10,
b: 11,
c: {
ca: 12,
cb: 13,
},
d: 14,
};
var res = [];
for( key of iterator(o) ) {
res.push(key);
}
res;
Output
var $__generatorWrap = function(generator) {
return $traceurRuntime.addIterator({
next: function(x) {
switch (generator.GState) {
case 1:
throw new Error('"next" on executing generator');
case 3:
throw new Error('"next" on closed generator');
case 0:
if (x !== undefined) {
throw new TypeError('Sent value to newborn generator');
}
case 2:
generator.GState = 1;
if (generator.moveNext(x, 0)) {
generator.GState = 2;
return {
value: generator.current,
done: false
};
}
generator.GState = 3;
return {
value: generator.yieldReturn,
done: true
};
}
},
'throw': function(x) {
switch (generator.GState) {
case 1:
throw new Error('"throw" on executing generator');
case 3:
throw new Error('"throw" on closed generator');
case 0:
generator.GState = 3;
throw x;
case 2:
generator.GState = 1;
if (generator.moveNext(x, 1)) {
generator.GState = 2;
return {
value: generator.current,
done: false
};
}
generator.GState = 3;
return {
value: generator.yieldReturn,
done: true
};
}
}
});
};
var iterator = function(object) {
var $that = this;
var $arguments = arguments;
var $state = 20;
var $storedException;
var $finallyFallThrough;
var $__0;
var $__1;
var $__2;
var $__3;
var $__4;
var $__5;
var key;
var $G = {
GState: 0,
current: undefined,
yieldReturn: undefined,
innerFunction: function($yieldSent, $yieldAction) {
while (true) switch ($state) {
case 20:
$__2 = [];
$state = 21;
break;
case 21:
$__3 = object;
$state = 23;
break;
case 23:
for (var $__4 in $__3) $__2.push($__4);
$state = 25;
break;
case 25:
$__5 = 0;
$state = 17;
break;
case 17:
if ($__5 < $__2.length) {
$state = 12;
break;
} else {
$state = 19;
break;
}
case 11:
$__5++;
$state = 17;
break;
case 12:
key = $__2[$__5];
$state = 13;
break;
case 13:
if (!(key in $__3)) {
$state = 11;
break;
} else {
$state = 15;
break;
}
case 15:
this.current = key;
$state = 1;
return true;
case 1:
if ($yieldAction == 1) {
$yieldAction = 0;
throw $yieldSent;
}
$state = 3;
break;
case 3:
$__0 = $traceurRuntime.getIterator(iterator(object[key]));
$state = 7;
break;
case 7:
if (!($__1 = $__0.next()).done) {
$state = 8;
break;
} else {
$state = 11;
break;
}
case 8:
k = $__1.value;
$state = 9;
break;
case 9:
this.current = k;
$state = 5;
return true;
case 5:
if ($yieldAction == 1) {
$yieldAction = 0;
throw $yieldSent;
}
$state = 7;
break;
case 19:
$state = -2;
case -2:
return false;
case -3:
throw $storedException;
default:
throw "traceur compiler bug: invalid state in state machine: " + $state;
}
},
moveNext: function($yieldSent, $yieldAction) {
while (true) try {
return this.innerFunction($yieldSent, $yieldAction);
} catch ($caughtException) {
$storedException = $caughtException;
switch ($state) {
default:
this.GState = 3;
$state = -2;
throw $storedException;
}
}
}
};
return $__generatorWrap($G);
};
var o = {
a: 10,
b: 11,
c: {
ca: 12,
cb: 13
},
d: 14
};
var res = [];
for (var $__1 = $traceurRuntime.getIterator(iterator(o)), $__0; !($__0 = $__1.next()).done;) {
key = $__0.value;
{
res.push(key);
}
}
res;
So yield statement in JavaScript, possible, but highly impractical.
What I actually ended up using
Usage example:
var object = {...};
var callback = function (key, value) {
// Do stuff...
return traverse.CONTINUE;
// or return traverse.STOP if you want the iteration to stop
};
traverse(object, callback);
Implementation:
var traverse = (function () {
var _traverse = function (object, callback) {
var key, value, command;
for( key in object ) {
value = object[key];
command = callback(key, value);
if( command === _traverse.STOP ) {
return _traverse.STOP;
}
command = _traverse(value, callback);
if( command === _traverse.STOP ) {
return _traverse.STOP;
}
}
};
_traverse.CONTINUE = 1;
_traverse.STOP = 2;
return _traverse;
})();
Related
I have following code
function MyFunc() {
var add = function (props) {
if (props.hasOwnProperty('a') && props.hasOwnProperty('b')) {
return 'ab';
} else if (props.hasOwnProperty('c')) {
return 'c';
} else if (props.hasOwnProperty('d')) {
return 'd';
} else {
throw new Error("Doomed!!!");
}
};
var div = function () {
return "Hello from div";
};
var methods = {
add: add,
div: div
};
var funcCall = function (obj) {
if (!obj) {
throw new Error("no Objects are passed");
}
return methods[obj.fName](obj.props);
};
return {
func: function (obj) {
return funcCall(obj);
}
};
}
var lol = new MyFunc();
When lol.func({fName: 'add', props: {a: 'a', b: 'b'}}); is run it should return the correct response from add functions inner if else statements. But there can be more than 20 else if occurrences. My question is will this be a reason for bad performance, Is there any alternative approch for achieving this
DEMO
UPDATE
Another question
Could someone please explain me how to implement map based conditioning for this code
You could use a switch statement but more complex logic such as && starts to get more complicated (they are really designed for just quick one to one comparisons). I would stick with what you have if you want that more complex logic. It is technically the slowest but if all the other ways are very complicated to implement the gain in performance will not be worth it.
you can use switch statements
http://www.w3schools.com/js/js_switch.asp
My question is will this be a reason for bad performance
Well in all cases the logic show that you need to do some check before returning the name of these properties.
So If you care about performance you need to decrease the time of each check.
What you could do is retrieve the list of props properties instead of doing hasOwnProperty in each if statements, and use the list to do the check with indexOf.
var propsList = Object.keys(props);
if (propsList.indexOf("a") > 0 && propsList.indexOf("b") > 0){
return "ab";
}
With this if you want avoid if statements, you could use a for loop and an Array that will contain all the properties
var add = function (props) {
var listKeys = ["ab", "c", "d"]; // List of the properties
var objectKeys = Object.keys(props);
for (var i = 0, len = listKeys.length; i < len ; i++){
var listProps = listKeys[i].split("");
var ok = true;
for (var j = 0, len2 = listProps.length; j < len2 ; j++){
if (objectKeys.indexOf(listProps[j]) < 0){
ok = false;
break;
}
}
if (ok){
return listKeys[i];
}
}
throw new Error("Doomed!!!");
}
I have experimented a more generic solution as follows:
function MyFunc() {
// Check if all 'searchedProps[]' properties are contained in 'obj';
// If one property is not found it will return false;
// If all properties are found it will return true;
function hasProperties(obj, searchedProps) {
for (var i = 0; i < searchedProps.length; i++) {
if (!obj.hasOwnProperty(searchedProps[i])) {
return false;
}
}
return true;
}
var add = function (props) {
var options = [
{
properties: ["a", "b"],
result: "ab"
},
{
properties: ["c"],
result: "c"
},
{
properties: ["d"],
result: "d"
}];
for (var i = 0; i < options.length; i++) {
if (hasProperties(props, options[i].properties)) {
return options[i].result;
}
}
throw new Error("Doomed!!!");
};
var div = function () {
return "Hello from div";
};
var methods = {
add: add,
div: div
};
var funcCall = function (obj) {
if (!obj) {
throw new Error("no Objects are passed");
}
return methods[obj.fName](obj.props);
};
return {
func: function (obj) {
return funcCall(obj);
}
};
}
var lol = new MyFunc();
var result = lol.func({ fName: 'add', props: { a: 'a', b: 'b' } });
console.log(result);
Please check the result in console.
You could try to use Swtich case (http://www.w3schools.com/js/js_switch.asp)
Don't know if this will work in your case.
switch(probs){
case "(props.hasOwnProperty('c'))"
return 'c';
case "(props.hasOwnProperty('d'))"
return 'd'; }
should look like this at the end
I need to transform a string
1. "user.member.staffAddress"
2. "user.something"
to object:
1. { user: { member: { staffAddress: {} } } }
2. { user: { something: {} } }
Does anyone has an elegant way how to do it? It should always be an object in object. The last property should be an empty one.
I wrote a utility that I think you will find helpful for this:
https://github.com/forms-js/forms-js/blob/master/source/utils/flatten.ts
Here's the relevant bits. It's written in TypeScript but if you remove the :type annotations, it's valid JavaScript.
/**
* Writes a value to the location specified by a flattened key and creates nested structure along the way as needed.
*
* <p>For example, writing "baz" to the key 'foo.bar' would result in an object <code>{foo: {bar: "baz"}}</code>.
* Writing 3 to the key 'foo[0].bar' would result in an object <code>{foo: [{bar: 3}]}</code>.
*/
function write(value:any, flattenedKey:string, object:any):void {
var currentKey:any;
var keyIndexStart = 0;
for (var charIndex = 0, length = flattenedKey.length; charIndex < length; charIndex++) {
var character = flattenedKey.charAt(charIndex);
switch(character) {
case '[':
currentKey = flattenedKey.substring(keyIndexStart, charIndex);
createPropertyIfMissing_(currentKey, object, Array);
break;
case ']':
currentKey = flattenedKey.substring(keyIndexStart, charIndex);
currentKey = parseInt(currentKey); // Convert index from string to int
// Special case where we're targeting this object in the array
if (charIndex === length - 1) {
object[currentKey] = value;
} else {
// If this is the first time we're accessing this Array key we may need to initialize it.
if (!object[currentKey] && charIndex < length - 1) {
switch(flattenedKey.charAt(charIndex + 1)) {
case '[':
object[currentKey] = [];
break;
case '.':
object[currentKey] = {};
break;
}
}
object = object[currentKey];
}
break;
case '.':
currentKey = flattenedKey.substring(keyIndexStart, charIndex);
// Don't do anything with empty keys that follow Array indices (e.g. anArray[0].aProp)
if (currentKey) {
createPropertyIfMissing_(currentKey, object, Object);
}
break;
default:
continue; // Continue to iterate...
break;
}
keyIndexStart = charIndex + 1;
if (currentKey) {
object = object[currentKey];
}
}
if (keyIndexStart < flattenedKey.length) {
currentKey = flattenedKey.substring(keyIndexStart, flattenedKey.length);
object[currentKey] = value;
}
}
/**
* Helper method for initializing a missing property.
*
* #throws Error if unrecognized property specified
* #throws Error if property already exists of an incorrect type
*/
function createPropertyIfMissing_(key:string, object:any, propertyType:any):void {
switch(propertyType) {
case Array:
if (!object.hasOwnProperty(key)) {
object[key] = [];
} else if (!(object[key] instanceof Array)) {
throw Error('Property already exists but is not an Array');
}
break;
case Object:
if (!object.hasOwnProperty(key)) {
object[key] = {};
} else if (typeof object[key] !== 'object') {
throw Error('Property already exists but is not an Object');
}
break;
default:
throw Error('Unsupported property type');
break;
}
}
To be fair, you could also consider a project written specifically for doing this - rather than mine, in which it's only a small portion - which is to say, https://github.com/hughsk/flat
Iterate and add the properties etc ...
function stringToObject(str) {
var obj = {}, arr = str.split('.');
(function it(o) {
var key = arr.shift();
o[key] = {};
if (arr.length) it(o[key]);
}(obj));
return obj;
}
var obj = stringToObject("user.member.staffAddress");
document.body.innerHTML = JSON.stringify(obj, null, 4);
A string conversion approach:
var str = 'user.member.staffAddress'
var str_arr = str.split('.')
var obj = JSON.parse(
'{ "' + str_arr.join('": { "') + '": {}'
+ Array(str_arr.length+1).join(' }')
)
console.log(obj)
// { "user": { "member": { "staffAddress": {} } } }
Split the string.
Join elements with ": { ".
Wrap new string with { " and ": {} followed by length number of closing curly braces.
Parse final string as JSON object.
JSFiddle Demo
Is there a way to clear an object in Javascript? Specifically if an object has several member variables is there a simple way to reset each value?
function exampleObject() {
this.valueA = "A";
this.valueB = "B";
this.myArray = [1,2,3];
}
So basically for an instance of the above reset the three members to empty strings and an empty array? I could easily prototype a member function for the object to do this and call it:
function exampleObject() {
this.valueA = "A";
this.valueB = "B";
this.myArray = [1,2,3];
exampleObject.prototype.resetAll = function() {
this.valueA = "";
this.valueB = "";
this.myArray = [];
}
}
However I want to do this for objects from a third-party library so adding a member function isn't realistic.
It depends on what you mean by "reset". If you want to clear all properties to some default value based on property type, you could do something like this:
function resetObject(o) {
for(var key in o) {
if(!o.hasOwnProperty(key)) continue;
var val = o[key];
switch(typeof val) {
case "string":
o[key] = ""; break;
case "number":
o[key] = 0; break;
case "boolean":
o[key] = false; break;
case "object":
if(val === null) break;
if(val instanceof Array) {
o[key] = []; break;
}
val = {};
//Or recursively clear the sub-object
//resetObject(val);
break;
}
}
}
This might need a little tweaking if you have own properties which are again Objects (or functions), but you could do something like..
var resetMyObject = (function (Constr) {
var defaults = new Constr();
function has(o, k) {
return Object.prototype.hasOwnProperty.call(o, k);
}
return function resetFunction(obj) {
var k;
for (k in obj) { // might want to iterate over ownPropertyNames instead
if (has(obj, k)) {
if (has(defaults, k)) {
obj[k] = defaults[k];
} else {
delete obj[k];
}
}
}
return obj;
};
}(MyObject)); // pass in the constructor to generate the function
resetMyObject(instanceOfMyObject);
For example, with MyObject = Array,
resetMyObject([1, 2, 3]); // [undefined × 3] (length non-enumerable property)
I'm looking for an efficient way to find out whether two arrays contain same amounts of equal elements (in the == sense), in any order:
foo = {/*some object*/}
bar = {/*some other object*/}
a = [1,2,foo,2,bar,2]
b = [bar,2,2,2,foo,1]
sameElements(a, b) --> true
PS. Note that pretty much every solution in the thread uses === and not == for comparison. This is fine for my needs though.
Update 5
I posted a new answer with a different approach.
Update
I extended the code to have the possibility of either checking by reference or equality
just pass true as second parameter to do a reference check.
Also I added the example to Brunos JSPerf
It runs at about 11 ops/s doing a reference check
I will comment the code as soon(!) as I get some spare time to explain it a bit more, but at the moment don't have the time for that, sry. Done
Update 2.
Like Bruno pointed out in the comments sameElements([NaN],[NaN]) yields false
In my opinion this is the correct behaviour as NaN is ambigious and should always lead to a false result,at least when comparing NaN.equals(NaN). But he had quite a good point.
Whether
[1,2,foo,bar,NaN,3] should be equal to [1,3,foo,NaN,bar,2] or not.
Ok.. honestly I'm a bit torn whether it should or not, so i added two flags.
Number.prototype.equal.NaN
If true
NaN.equals(NaN) //true
Array.prototype.equal.NaN
If true
[NaN].equals([NaN],true) //true
note this is only for reference checks. As a deep check would invoke Number.prototype.equals anyway
Update 3:
Dang i totally missed 2 lines in the sort function.
Added
r[0] = a._srt; //DANG i totally missed this line
r[1] = b._srt; //And this.
Line 105 in the Fiddle
Which is kind of important as it determines the consistent order of the Elements.
Update 4
I tried to optimize the sort function a bit, and managed to get it up to about 20 ops/s.
Below is the updated code, as well as the updated fiddle =)
Also i chose to mark the objects outside the sort function, it doesn't seem to make a performance difference anymore, and its more readable
Here is an approach using Object.defineProperty to add equals functions to
Array,Object,Number,String,Boolean's prototype to avoid typechecking in one function for
performance reasons. As we can recursively call .equals on any element.
But of course checking Objects for equality may cause performance issues in big Objects.
So if anyone feels unpleasant manipulating native prototypes, just do a type check and put it into one function
Object.defineProperty(Boolean.prototype, "equals", {
enumerable: false,
configurable: true,
value: function (c) {
return this == c; //For booleans simply return the equality
}
});
Object.defineProperty(Number.prototype, "equals", {
enumerable: false,
configurable: true,
value: function (c) {
if (Number.prototype.equals.NaN == true && isNaN(this) && c != c) return true; //let NaN equals NaN if flag set
return this == c; // else do a normal compare
}
});
Number.prototype.equals.NaN = false; //Set to true to return true for NaN == NaN
Object.defineProperty(String.prototype, "equals", {
enumerable: false,
configurable: true,
value: Boolean.prototype.equals //the same (now we covered the primitives)
});
Object.defineProperty(Object.prototype, "equals", {
enumerable: false,
configurable: true,
value: function (c, reference) {
if (true === reference) //If its a check by reference
return this === c; //return the result of comparing the reference
if (typeof this != typeof c) {
return false; //if the types don't match (Object equals primitive) immediately return
}
var d = [Object.keys(this), Object.keys(c)],//create an array with the keys of the objects, which get compared
f = d[0].length; //store length of keys of the first obj (we need it later)
if (f !== d[1].length) {//If the Objects differ in the length of their keys
return false; //immediately return
}
for (var e = 0; e < f; e++) { //iterate over the keys of the first object
if (d[0][e] != d[1][e] || !this[d[0][e]].equals(c[d[1][e]])) {
return false; //if either the key name does not match or the value does not match, return false. a call of .equal on 2 primitives simply compares them as e.g Number.prototype.equal gets called
}
}
return true; //everything is equal, return true
}
});
Object.defineProperty(Array.prototype, "equals", {
enumerable: false,
configurable: true,
value: function (c,reference) {
var d = this.length;
if (d != c.length) {
return false;
}
var f = Array.prototype.equals.sort(this.concat());
c = Array.prototype.equals.sort(c.concat(),f)
if (reference){
for (var e = 0; e < d; e++) {
if (f[e] != c[e] && !(Array.prototype.equals.NaN && f[e] != f[e] && c[e] != c[e])) {
return false;
}
}
} else {
for (var e = 0; e < d; e++) {
if (!f[e].equals(c[e])) {
return false;
}
}
}
return true;
}
});
Array.prototype.equals.NaN = false; //Set to true to allow [NaN].equals([NaN]) //true
Object.defineProperty(Array.prototype.equals,"sort",{
enumerable:false,
value:function sort (curr,prev) {
var weight = {
"[object Undefined]":6,
"[object Object]":5,
"[object Null]":4,
"[object String]":3,
"[object Number]":2,
"[object Boolean]":1
}
if (prev) { //mark the objects
for (var i = prev.length,j,t;i>0;i--) {
t = typeof (j = prev[i]);
if (j != null && t === "object") {
j._pos = i;
} else if (t !== "object" && t != "undefined" ) break;
}
}
curr.sort (sorter);
if (prev) {
for (var k = prev.length,l,t;k>0;k--) {
t = typeof (l = prev[k]);
if (t === "object" && l != null) {
delete l._pos;
} else if (t !== "object" && t != "undefined" ) break;
}
}
return curr;
function sorter (a,b) {
var tStr = Object.prototype.toString
var types = [tStr.call(a),tStr.call(b)]
var ret = [0,0];
if (types[0] === types[1] && types[0] === "[object Object]") {
if (prev) return a._pos - b._pos
else {
return a === b ? 0 : 1;
}
} else if (types [0] !== types [1]){
return weight[types[0]] - weight[types[1]]
}
return a>b?1:a<b?-1:0;
}
}
});
With this we can reduce the sameElements function to
function sameElements(c, d,referenceCheck) {
return c.equals(d,referenceCheck); //call .equals of Array.prototype.
}
Note. of course you could put all equal functions into the sameElements function, for the cost of the typechecking.
Now here are 3 examples: 1 with deep checking, 2 with reference checking.
var foo = {
a: 1,
obj: {
number: 2,
bool: true,
string: "asd"
},
arr: [1, 2, 3]
};
var bar = {
a: 1,
obj: {
number: 2,
bool: true,
string: "asd"
},
arr: [1, 2, 3]
};
var foobar = {
a: 1,
obj: {
number: 2,
bool: true,
string: "asd"
},
arr: [1, 2, 3, 4]
};
var a = [1, 2, foo, 2, bar, 2];
var b = [foo, 2, 2, 2, bar, 1];
var c = [bar, 2, 2, 2, bar, 1];
So these are the Arrays we compare. And the output is
Check a and b with references only.
console.log (sameElements ( a,b,true)) //true As they contain the same elements
Check b and c with references only
console.log (sameElements (b,c,true)) //false as c contains bar twice.
Check b and c deeply
console.log (sameElements (b,c,false)) //true as bar and foo are equal but not the same
Check for 2 Arrays containing NaN
Array.prototype.equals.NaN = true;
console.log(sameElements([NaN],[NaN],true)); //true.
Array.prototype.equals.NaN = false;
Demo on JSFiddle
You can implement the following algorithm:
If a and b do not have the same length:
Return false.
Otherwise:
Clone b,
For each item in a:
If the item exists in our clone of b:
Remove the item from our clone of b,
Otherwise:
Return false.
Return true.
With Javascript 1.6, you can use every() and indexOf() to write:
function sameElements(a, b)
{
if (a.length != b.length) {
return false;
}
var ourB = b.concat();
return a.every(function(item) {
var index = ourB.indexOf(item);
if (index < 0) {
return false;
} else {
ourB.splice(index, 1);
return true;
}
});
}
Note this implementation does not completely fulfill your requirements because indexOf() uses strict equality (===) internally. If you really want non-strict equality (==), you will have to write an inner loop instead.
Like this perhaps?
var foo = {}; var bar=[];
var a = [3,2,1,foo]; var b = [foo,1,2,3];
function comp(a,b)
{
// immediately discard if they are of different sizes
if (a.length != b.length) return false;
b = b.slice(0); // clone to keep original values after the function
a.forEach(function(e) {
var i;
if ((i = b.indexOf(e)) != -1)
b.splice(i, 1);
});
return !b.length;
}
comp(a,b);
UPDATE
As #Bergi and #thg435 point out my previous implementation was flawed so here is another implementation:
function sameElements(a, b) {
var objs = [];
// if length is not the same then must not be equal
if (a.length != b.length) return false;
// do an initial sort which will group types
a.sort();
b.sort();
for ( var i = 0; i < a.length; i++ ) {
var aIsPrimitive = isPrimitive(a[i]);
var bIsPrimitive = isPrimitive(b[i]);
// NaN will not equal itself
if( a[i] !== a[i] ) {
if( b[i] === b[i] ) {
return false;
}
}
else if (aIsPrimitive && bIsPrimitive) {
if( a[i] != b[i] ) return false;
}
// if not primitive increment the __count property
else if (!aIsPrimitive && !bIsPrimitive) {
incrementCountA(a[i]);
incrementCountB(b[i]);
// keep track on non-primitive objects
objs.push(i);
}
// if both types are not the same then this array
// contains different number of primitives
else {
return false;
}
}
var result = true;
for (var i = 0; i < objs.length; i++) {
var ind = objs[i];
// if __aCount and __bCount match then object exists same
// number of times in both arrays
if( a[ind].__aCount !== a[ind].__bCount ) result = false;
if( b[ind].__aCount !== b[ind].__bCount ) result = false;
// revert object to what it was
// before entering this function
delete a[ind].__aCount;
delete a[ind].__bCount;
delete b[ind].__aCount;
delete b[ind].__bCount;
}
return result;
}
// inspired by #Bergi's code
function isPrimitive(arg) {
return Object(arg) !== arg;
}
function incrementCountA(arg) {
if (arg.hasOwnProperty("__aCount")) {
arg.__aCount = arg.__aCount + 1;
} else {
Object.defineProperty(arg, "__aCount", {
enumerable: false,
value: 1,
writable: true,
configurable: true
});
}
}
function incrementCountB(arg) {
if (arg.hasOwnProperty("__bCount")) {
arg.__bCount = arg.__bCount + 1;
} else {
Object.defineProperty(arg, "__bCount", {
enumerable: false,
value: 1,
writable: true,
configurable: true
});
}
}
Then just call the function
sameElements( ["NaN"], [NaN] ); // false
// As "1" == 1 returns true
sameElements( [1],["1"] ); // true
sameElements( [1,2], [1,2,3] ); //false
The above implement actually defines a new property called "__count" that is used to keep track of non-primitive elements in both arrays. These are deleted before the function returns so as to leave the array elements as before.
Fiddle here
jsperf here.
The reason I changed the jsperf test case was that as #Bergi states the test arrays, especially the fact there were only 2 unique objects in the whole array is not representative of what we are testing for.
One other advantage of this implementation is that if you need to make it compatible with pre IE9 browsers instead of using the defineProperty to create a non-enumerable property you could just use a normal property.
Thanks everyone for sharing ideas! I've came up with the following
function sameElements(a, b) {
var hash = function(x) {
return typeof x + (typeof x == "object" ? a.indexOf(x) : x);
}
return a.map(hash).sort().join() == b.map(hash).sort().join();
}
This isn't the fastest solution, but IMO, most readable one so far.
i wasn't sure if "===" is ok, the question is a bit vauge...
if so, this is quite a bit faster and simpler than some other possible ways of doing it:
function isSame(a,b){
return a.length==b.length &&
a.filter(function(a){ return b.indexOf(a)!==-1 }).length == b.length;
}
Edit 2
1) Thanks to user2357112 for pointing out the Object.prototype.toString.call issue
this also showed, the reason it was that fast, that it didn't consider Arrays ...
I fixed the code,it should be working now :), unfortunately its now at about 59ops/s on chrome and 45ops/s on ff.
Fiddle and JSPerf is updated.
Edit
1)
I fixed the code, it supports mutliple variables referencing the same Object now.
A little bit slower than before, but still over 100ops/s on chrome.
2)
I tried using a bitmask instead of an array to keep multiple positions of the objects, but its nearly 15ops/s slow
3) As pointed ot in the comments i forgot to reset tmp after [[get]] is called fixed the code, the fiddle, and the perf.
So thanks to user2357112 with his Answer heres another approach using counting
var sameElements = (function () {
var f, of, objectFlagName;
of = objectFlagName = "__pos";
var tstr = function (o) {
var t = typeof o;
if (o === null)
t = "null";
return t
};
var types = {};
(function () {
var tmp = {};
Object.defineProperty(types, tstr(1), {
set: function (v) {
if (f)
tmp[v] = -~tmp[v];
else
tmp[v] = ~-tmp[v];
},
get: function () {
var ret = 1;
for (var k in tmp) {
ret &= !tmp[k];
}
tmp = {};
return ret;
}
});
})();
(function () {
var tmp = {};
Object.defineProperty(types, tstr(""), {
set: function (v) {
if (f) {
tmp[v] = -~tmp[v];
} else {
tmp[v] = ~-tmp[v];
}
},
get: function () {
var ret = 1;
for (var k in tmp) {
ret &= !tmp[k];
}
tmp = {};
return ret;
}
});
})();
(function () {
var tmp = [];
function add (v) {
tmp.push(v);
if (v[of]===undefined) {
v[of] = [tmp.length -1];
} else {
v[of].push(tmp.length -1)
}
}
Object.defineProperty(types, tstr({}), {
get: function () {
var ret = true;
for (var i = tmp.length - 1; i >= 0; i--) {
var c = tmp[i]
if (typeof c !== "undefined") {
ret = false
delete c[of]
}
}
tmp = [];
return ret;
},
set: function (v) {
var pos;
if (f) {
add (v);
} else if (!f && (pos = v[of]) !== void 0) {
tmp[pos.pop()] = undefined;
if (pos.length === 0)
delete v[of];
} else {
add (v);
}
}
});
}());
(function () {
var tmp = 0;
Object.defineProperty(types, tstr(undefined), {
get: function () {
var ret = !tmp;
tmp = 0;
return ret;
},
set: function () {
tmp += f ? 1 : -1;
}
});
})();
(function () {
var tmp = 0;
Object.defineProperty(types, tstr(null), {
get: function () {
var ret = !tmp;
tmp = 0;
return ret;
},
set: function () {
tmp += f ? 1 : -1;
}
});
})();
var tIt = [tstr(1), tstr(""), tstr({}), tstr(undefined), tstr(null)];
return function eq(a, b) {
f = true;
for (var i = a.length - 1; i >= 0; i--) {
var v = a[i];
types[tstr(v)] = v;
}
f = false;
for (var k = b.length - 1; k >= 0; k--) {
var w = b[k];
types[tstr(w)] = w;
}
var r = 1;
for (var l = 0, j; j = tIt[l]; l++) {
r &= types [j]
}
return !!r;
}
})()
Here is a JSFiddle and a JSPerf (it uses the same Arrays a and b as in the previous answers perf) with this code vs the Closure compiled
Heres the output. note: it doesn't support a deep comparison anymore, as is
var foo = {a:2}
var bar = {a:1};
var a = [1, 2, foo, 2, bar, 2];
var b = [foo, 2, 2, 2, bar, 1];
var c = [bar, 2, 2, 2, bar, 1];
console.log(sameElements([NaN],[NaN])); //true
console.log (sameElements ( a,b)) //true
console.log (sameElements (b,c)) //false
Using efficient lookup tables for the counts of the elements:
function sameElements(a) { // can compare any number of arrays
var map, maps = [], // counting booleans, numbers and strings
nulls = [], // counting undefined and null
nans = [], // counting nans
objs, counts, objects = [],
al = arguments.length;
// quick escapes:
if (al < 2)
return true;
var l0 = a.length;
if ([].slice.call(arguments).some(function(s) { return s.length != l0; }))
return false;
for (var i=0; i<al; i++) {
var multiset = arguments[i];
maps.push(map = {}); // better: Object.create(null);
objects.push({vals: objs=[], count: counts=[]});
nulls[i] = 0;
nans[i] = 0;
for (var j=0; j<l0; j++) {
var val = multiset[j];
if (val !== val)
nans[i]++;
else if (val === null)
nulls[i]++;
else if (Object(val) === val) { // non-primitive
var ind = objs.indexOf(val);
if (ind > -1)
counts[ind]++;
else
objs.push(val), counts.push(1);
} else { // booleans, strings and numbers do compare together
if (typeof val == "boolean")
val = +val;
if (val in map)
map[val]++;
else
map[val] = 1;
}
}
}
// testing if nulls and nans are the same everywhere
for (var i=1; i<al; i++)
if (nulls[i] != nulls[0] || nans[i] != nans[0])
return false;
// testing if primitives were the same everywhere
var map0 = maps[0];
for (var el in map0)
for (var i=1; i<al; i++) {
if (map0[el] !== maps[i][el])
return false;
delete maps[i][el];
}
for (var i=1; i<al; i++)
for (var el in maps[i])
return false;
// testing if objects were the same everywhere
var objs0 = objects[0].vals,
ol = objs0.length;
counts0 = objects[0].count;
for (var i=1; i<al; i++)
if (objects[i].count.length != ol)
return false;
for (var i=0; i<ol; i++)
for (var j=1; j<al; j++)
if (objects[j].count[ objects[j].vals.indexOf(objs0[i]) ] != counts0[i])
return false;
// else, the multisets are equal:
return true;
}
It still uses indexOf search amongst all objects, so if you have multisets with many different objects you might want to optimize that part as well. Have a look at Unique ID or object signature (and it's duplicate questions) for how to get lookup table keys for them. And if you don't have many primitive values in the multisets, you might just store them in arrays and sort those before comparing each item-by-item (like #Bruno did).
Disclaimer: This solution doesn't try to get the [[PrimitiveValue]] of objects, they will never be counted as equal to primitives (while == would do).
Here is the update on #Bruno's jsperf test of the answers, yet I guess only two objects (each of them present 500 times in the 10k array) and no duplicate primitive values are not representative.
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
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.
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.
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
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.
I know that I am a year late, but for all others who stumble upon this thread, I've written the ordered object stringify to JSON, that solves the above noted dilemma: http://stamat.wordpress.com/javascript-object-ordered-property-stringify/
Also I was playing with custom hash table implementations which is also related to the topic: http://stamat.wordpress.com/javascript-quickly-find-very-large-objects-in-a-large-array/
//SORT WITH STRINGIFICATION
var orderedStringify = function(o, fn) {
var props = [];
var res = '{';
for(var i in o) {
props.push(i);
}
props = props.sort(fn);
for(var i = 0; i < props.length; i++) {
var val = o[props[i]];
var type = types[whatis(val)];
if(type === 3) {
val = orderedStringify(val, fn);
} else if(type === 2) {
val = arrayStringify(val, fn);
} else if(type === 1) {
val = '"'+val+'"';
}
if(type !== 4)
res += '"'+props[i]+'":'+ val+',';
}
return res.substring(res, res.lastIndexOf(','))+'}';
};
//orderedStringify for array containing objects
var arrayStringify = function(a, fn) {
var res = '[';
for(var i = 0; i < a.length; i++) {
var val = a[i];
var type = types[whatis(val)];
if(type === 3) {
val = orderedStringify(val, fn);
} else if(type === 2) {
val = arrayStringify(val);
} else if(type === 1) {
val = '"'+val+'"';
}
if(type !== 4)
res += ''+ val+',';
}
return res.substring(res, res.lastIndexOf(','))+']';
}
Based on Peters answer, but with proper class design (not abusing closures), so the values are debuggable. Renamed from Map to ObjectMap, because Map is a builtin function. Also added the exists method:
ObjectMap = function() {
this.keys = [];
this.values = [];
}
ObjectMap.prototype.set = function(key, value) {
var index = this.keys.indexOf(key);
if (index == -1) {
this.keys.push(key);
this.values.push(value);
} else {
this.values[index] = value;
}
}
ObjectMap.prototype.get = function(key) {
return this.values[ this.keys.indexOf(key) ];
}
ObjectMap.prototype.exists = function(key) {
return this.keys.indexOf(key) != -1;
}
/*
TestObject = function() {}
testA = new TestObject()
testB = new TestObject()
om = new ObjectMap()
om.set(testA, true)
om.get(testB)
om.exists(testB)
om.exists(testA)
om.exists(testB)
*/
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
Using JSON.stringify() is completely awkward to me, and gives the client no real control over how their keys are uniquely identified. The objects that are used as keys should have a hashing function, but my guess is that in most cases overriding the toString() method, so that they will return unique strings, will work fine:
var myMap = {};
var myKey = { toString: function(){ return '12345' }};
var myValue = 6;
// same as myMap['12345']
myMap[myKey] = myValue;
Obviously, toString() should do something meaningful with the object's properties to create a unique string. If you want to enforce that your keys are valid, you can create a wrapper and in the get() and put() methods, add a check like:
if(!key.hasOwnProperty('toString')){
throw(new Error('keys must override toString()'));
}
But if you are going to go thru that much work, you may as well use something other than toString(); something that makes your intent more clear.
So a very simple proposal would be:
function HashTable() {
this.hashes = {};
}
HashTable.prototype = {
constructor: HashTable,
put: function( key, value ) {
// check that the key is meaningful,
// also will cause an error if primitive type
if( !key.hasOwnProperty( 'hashString' ) ){
throw( new Error( 'keys must implement hashString()' ) );
}
// use .hashString() because it makes the intent of the code clear
this.hashes[ key.hashString() ] = value;
},
get: function( key ) {
// check that the key is meaningful,
// also will cause an error if primitive type
if( !key.hasOwnProperty( 'hashString' ) ){
throw( new Error( 'keys must implement hashString()' ) );
}
// use .hashString() because it make the intent of the code clear
return this.hashes[ key.hashString() ];
}
};
Inspired by #florian, here's a way where the id doesn't need JSON.stringify:
'use strict';
module.exports = HashTable;
function HashTable () {
this.index = [];
this.table = [];
}
HashTable.prototype = {
constructor: HashTable,
set: function (id, key, value) {
var index = this.index.indexOf(id);
if (index === -1) {
index = this.index.length;
this.index.push(id);
this.table[index] = {};
}
this.table[index][key] = value;
},
get: function (id, key) {
var index = this.index.indexOf(id);
if (index === -1) {
return undefined;
}
return this.table[index][key];
}
};
I took #Ilya_Gazman solution and improved it by setting '_hashtableUniqueId' as a not enumerable property (it won't appear in JSON requests neither will be listed in for loops). Also removed UniqueId object, since it is enough using only HastTable function closure. For usage details please see Ilya_Gazman post
function HashTable() {
var hash = new Object();
return {
put: function (key, value) {
if(!HashTable.uid){
HashTable.uid = 0;
}
if (typeof key === "string") {
hash[key] = value;
} else {
if (key._hashtableUniqueId === undefined) {
Object.defineProperty(key, '_hashtableUniqueId', {
enumerable: false,
value: HashTable.uid++
});
}
hash[key._hashtableUniqueId] = value;
}
},
get: function (key) {
if (typeof key === "string") {
return hash[key];
}
if (key._hashtableUniqueId === undefined) {
return undefined;
}
return hash[key._hashtableUniqueId];
}
};
}
The best solution is to use WeakMap when you can (i.e. when you target browsers supporting it)
Otherwise you can use the following workaround (Typescript written and collision safe):
// Run this in the beginning of your app (or put it into a file you just import)
(enableObjectID)();
const uniqueId: symbol = Symbol('The unique id of an object');
function enableObjectID(): void {
if (typeof Object['id'] !== 'undefined') {
return;
}
let id: number = 0;
Object['id'] = (object: any) => {
const hasUniqueId: boolean = !!object[uniqueId];
if (!hasUniqueId) {
object[uniqueId] = ++id;
}
return object[uniqueId];
};
}
Then you can simply get a unique number for any object in your code (like if would have been for pointer address)
let objectA = {};
let objectB = {};
let dico = {};
dico[(<any>Object).id(objectA)] = "value1";
// or
dico[Object['id'](objectA);] = "value1";
// If you are not using typescript you don't need the casting
dico[Object.id(objectA)] = "value1"
I know I'm late, but here's a simple HashMap implementation:
Function.prototype.toJSON = Function.prototype.toString;
//taken from https://stackoverflow.com/questions/1249531/how-to-get-a-javascript-objects-class
function getNativeClass(obj) {
if (typeof obj === "undefined") return "undefined";
if (obj === null) return "null";
return Object.prototype.toString.call(obj).match(/^\[object\s(.*)\]$/)[1];
}
function globals() {
if (typeof global === "object") //node
return global;
return this;
}
function lookup(x) {
return globals()[x];
}
function getAnyClass(obj) {
if (typeof obj === "undefined") return "undefined";
if (obj === null) return "null";
return obj.constructor.name;
}
//taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cyclic_object_value#examples
var getCircularReplacer = () => {
const seen = new WeakSet();
return (key, value) => {
if (typeof value === "object" && value !== null) {
if (seen.has(value)) {
return "[Circular]";
}
seen.add(value);
}
return value;
};
};
function encode(x) {
if (typeof x === "object" && x !== null) {
var y = myClone(x);
x = Object.getPrototypeOf(x);
for (var i = 0; i < Object.getOwnPropertyNames(y).length; i++) { //Make enumerable
x[Object.getOwnPropertyNames(y)[i]] = y[Object.getOwnPropertyNames(y)[i]];
}
}
return getAnyClass(x) + " " + JSON.stringify(x, getCircularReplacer());
}
function decode(x) {
var a = x.split(" ").slice(1).join(" "); //OBJECT
if (typeof lookup(x.split(" ")[0])) {
return new (lookup(x.split(" ")[0]))(JSON.parse(a))
} else {
return JSON.parse(a);
}
}
//taken from https://github.com/feross/fromentries/blob/master/index.js
/*! fromentries. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
function fromEntries(iterable) {
return [...iterable].reduce((obj, [key, val]) => {
obj[key] = val
return obj
}, {})
}
var toEnumerable = (obj) => {
return fromEntries(
Object.getOwnPropertyNames(obj).map(prop => [prop, obj[prop]])
);
};
//taken from https://stackoverflow.com/questions/41474986/how-to-clone-a-javascript-es6-class-instance
function myClone(instanceOfBlah) {
if (typeof instanceOfBlah !== "object" || !instanceOfBlah) { return instanceOfBlah; }
const clone = Object.assign({}, toEnumerable(instanceOfBlah));
const Blah = instanceOfBlah.constructor;
Object.setPrototypeOf(clone, Blah.prototype);
return clone;
}
function HashMap(a) {
if (typeof a === "undefined") {
a = [];
}
a = Array.from(a);
a = a.map((e) => [encode(e[0]), e[1]]);
this.a = a;
}
HashMap.from = function (a) {
var temp = myClone(a);
//convert to array
a = [];
for (var i = 0; i < Object.getOwnPropertyNames(temp).length; i++) {
a.push([Object.getOwnPropertyNames(temp)[i], temp[Object.getOwnPropertyNames(temp)[i]]]);
}
return new HashMap(a);
}
HashMap.prototype.put = function (x, y) {
this.a.push([encode(x), y]);
}
HashMap.prototype.get = function (x) {
var t1 = this.a.map((e) => e[0]);
return this.a[t1.indexOf(encode(x))][1];
}
HashMap.prototype.length = function () {
return this.a.length;
}
HashMap.prototype.toString = function () {
var result = [];
for (var i = 0; i < this.length(); i++) {
result.push(JSON.stringify(decode(this.a[i][0]), getCircularReplacer()) + " => " + this.a[i][1]);
}
return "HashMap {" + result + "}";
}
var foo = new HashMap();
foo.put("SQRT3", Math.sqrt(3));
foo.put({}, "bar");
console.log(foo.get({}));
console.log(foo.toString());
Note that it is ordered.
Methods:
put: Adds an item
get: Access an item
from (static): Convert from a JavaScript object
toString: Convert to string
Minified and without the test:
function getNativeClass(t){return void 0===t?"undefined":null===t?"null":Object.prototype.toString.call(t).match(/^\[object\s(.*)\]$/)[1]}function globals(){return"object"==typeof global?global:this}function lookup(t){return globals()[t]}function getAnyClass(t){return void 0===t?"undefined":null===t?"null":t.constructor.name}Function.prototype.toJSON=Function.prototype.toString;var getCircularReplacer=()=>{const t=new WeakSet;return(e,r)=>{if("object"==typeof r&&null!==r){if(t.has(r))return"[Circular]";t.add(r)}return r}};function encode(t){if("object"==typeof t&&null!==t){var e=myClone(t);t=Object.getPrototypeOf(t);for(var r=0;r<Object.getOwnPropertyNames(e).length;r++)t[Object.getOwnPropertyNames(e)[r]]=e[Object.getOwnPropertyNames(e)[r]]}return getAnyClass(t)+" "+JSON.stringify(t,getCircularReplacer())}function decode(t){var e=t.split(" ").slice(1).join(" ");return lookup(t.split(" ")[0]),new(lookup(t.split(" ")[0]))(JSON.parse(e))}function fromEntries(t){return[...t].reduce((t,[e,r])=>(t[e]=r,t),{})}var toEnumerable=t=>fromEntries(Object.getOwnPropertyNames(t).map(e=>[e,t[e]]));function myClone(t){if("object"!=typeof t||!t)return t;const e=Object.assign({},toEnumerable(t)),r=t.constructor;return Object.setPrototypeOf(e,r.prototype),e}function HashMap(t){void 0===t&&(t=[]),t=(t=Array.from(t)).map(t=>[encode(t[0]),t[1]]),this.a=t}HashMap.from=function(t){var e=myClone(t);t=[];for(var r=0;r<Object.getOwnPropertyNames(e).length;r++)t.push([Object.getOwnPropertyNames(e)[r],e[Object.getOwnPropertyNames(e)[r]]]);return new HashMap(t)},HashMap.prototype.put=function(t,e){this.a.push([encode(t),e])},HashMap.prototype.get=function(t){var e=this.a.map(t=>t[0]);return this.a[e.indexOf(encode(t))][1]},HashMap.prototype.length=function(){return this.a.length},HashMap.prototype.toString=function(){for(var t=[],e=0;e<this.length();e++)t.push(JSON.stringify(decode(this.a[e][0]),getCircularReplacer())+" => "+this.a[e][1]);return"HashMap {"+t+"}"};
Also, you can customize the encoder and decoder by changing encode and decode functions.
As in florian's answer, you can't play with the reference in js however (so two empty objects will look like the same to the hashtable).
class Dict{
constructor(){
this.keys = [];
this.values = [];
this.set = this.set.bind(this);
}
set(key, value){
this.keys.push(key);
this.values.push(value);
}
get(key){
return this.values[this.keys.indexOf(key)];
}
all(){
return this.keys.map((kk, ii)=>[kk, this.values[ii]]);
}
}
let d1 = new Dict();
let k1 = {1: 'a'};
d1.set(k1, 2);
console.log(d1.get(k1)); // 2
let k2 = {2: 'b'};
d1.set(k2, 3);
console.log(d1.all());
// [ [ { '1': 'a' }, 2 ], [ { '2': 'b' }, 3 ] ]
Just use the strict equality operator when looking up the object: ===
var objects = [];
objects.push(object1);
objects.push(object2);
objects[0] === object1; // true
objects[1] === object1; // false
The implementation will depend on how you store the objects in the HashTable class.