Related
Suppose we are only given
var obj = {};
var propName = "foo.bar.foobar";
How can we set the property obj.foo.bar.foobar to a certain value (say "hello world")?
So I want to achieve this, while we only have the property name in a string:
obj.foo.bar.foobar = "hello world";
function assign(obj, prop, value) {
if (typeof prop === "string")
prop = prop.split(".");
if (prop.length > 1) {
var e = prop.shift();
assign(obj[e] =
Object.prototype.toString.call(obj[e]) === "[object Object]"
? obj[e]
: {},
prop,
value);
} else
obj[prop[0]] = value;
}
var obj = {},
propName = "foo.bar.foobar";
assign(obj, propName, "Value");
I know it's an old one, but I see only custom functions in answers.
If you don't mind using a library, look at lodash _.set and _.get function.
Since this question appears to be answered by incorrect answers, I'll just refer to the correct answer from a similar question
function setDeepValue(obj, value, path) {
if (typeof path === "string") {
var path = path.split('.');
}
if(path.length > 1){
var p=path.shift();
if(obj[p]==null || typeof obj[p]!== 'object'){
obj[p] = {};
}
setDeepValue(obj[p], value, path);
}else{
obj[path[0]] = value;
}
}
Use:
var obj = {};
setDeepValue(obj, 'Hello World', 'foo.bar.foobar');
edit: I've created a jsPerf.com testcase to compare the accepted answer with my version.
Turns out that my version is faster, especially when you go very deep.
http://jsfiddle.net/9YMm8/
var nestedObjectAssignmentFor = function(obj, propString, value) {
var propNames = propString.split('.'),
propLength = propNames.length-1,
tmpObj = obj;
for (var i = 0; i <= propLength ; i++) {
tmpObj = tmpObj[propNames[i]] = i !== propLength ? {} : value;
}
return obj;
}
var obj = nestedObjectAssignment({},"foo.bar.foobar","hello world");
All solutions overid any of the original data when setting so I have tweaked with the following, made it into a single object too:
var obj = {}
nestObject.set(obj, "a.b", "foo");
nestObject.get(obj, "a.b"); // returns foo
var nestedObject = {
set: function(obj, propString, value) {
var propNames = propString.split('.'),
propLength = propNames.length-1,
tmpObj = obj;
for (var i = 0; i <= propLength ; i++) {
if (i === propLength){
if(tmpObj[propNames[i]]){
tmpObj[propNames[i]] = value;
}else{
tmpObj[propNames[i]] = value;
}
}else{
if(tmpObj[propNames[i]]){
tmpObj = tmpObj[propNames[i]];
}else{
tmpObj = tmpObj[propNames[i]] = {};
}
}
}
return obj;
},
get: function(obj, propString){
var propNames = propString.split('.'),
propLength = propNames.length-1,
tmpObj = obj;
for (var i = 0; i <= propLength ; i++) {
if(tmpObj[propNames[i]]){
tmpObj = tmpObj[propNames[i]];
}else{
break;
}
}
return tmpObj;
}
};
Can also change functions to be an Oject.prototype method changing obj param to this:
Object.prototype = { setNested = function(){ ... }, getNested = function(){ ... } }
{}.setNested('a.c','foo')
Here is a get and set function i just compiled from a couple of threads + some custom code.
It will also create keys that don't exist on set.
function setValue(object, path, value) {
var a = path.split('.');
var o = object;
for (var i = 0; i < a.length - 1; i++) {
var n = a[i];
if (n in o) {
o = o[n];
} else {
o[n] = {};
o = o[n];
}
}
o[a[a.length - 1]] = value;
}
function getValue(object, path) {
var o = object;
path = path.replace(/\[(\w+)\]/g, '.$1');
path = path.replace(/^\./, '');
var a = path.split('.');
while (a.length) {
var n = a.shift();
if (n in o) {
o = o[n];
} else {
return;
}
}
return o;
}
Here is a simple function to do that using reference.
function setValueByPath (obj, path, value) {
var ref = obj;
path.split('.').forEach(function (key, index, arr) {
ref = ref[key] = index === arr.length - 1 ? value : {};
});
return obj;
}
You could split the path and make a check if the following element exist. If not assign an object to the new property.
Return then the value of the property.
At the end assign the value.
function setValue(object, path, value) {
var fullPath = path.split('.'),
way = fullPath.slice(),
last = way.pop();
way.reduce(function (r, a) {
return r[a] = r[a] || {};
}, object)[last] = value;
}
var object = {},
propName = 'foo.bar.foobar',
value = 'hello world';
setValue(object, propName, value);
console.log(object);
Here's one that returns the updated object
function deepUpdate(value, path, tree, branch = tree) {
const last = path.length === 1;
branch[path[0]] = last ? value : branch[path[0]];
return last ? tree : deepUpdate(value, path.slice(1), tree, branch[path[0]]);
}
const path = 'cat.dog';
const updated = deepUpdate('a', path.split('.'), {cat: {dog: null}})
// => { cat: {dog: 'a'} }
A very straightforward one.
This implementation should be very performant.
It avoids recursions, and function calls, while maintaining simplicity.
/**
* Set the value of a deep property, creating new objects as necessary.
* #param {Object} obj The object to set the value on.
* #param {String|String[]} path The property to set.
* #param {*} value The value to set.
* #return {Object} The object at the end of the path.
* #author github.com/victornpb
* #see https://stackoverflow.com/a/46060952/938822
* #example
* setDeep(obj, 'foo.bar.baz', 'quux');
*/
function setDeep(obj, path, value) {
const props = typeof path === 'string' ? path.split('.') : path;
for (var i = 0, n = props.length - 1; i < n; ++i) {
obj = obj[props[i]] = obj[props[i]] || {};
}
obj[props[i]] = value;
return obj;
}
/*********************** EXAMPLE ***********************/
const obj = {
hello : 'world',
};
setDeep(obj, 'root', true);
setDeep(obj, 'foo.bar.baz', 1);
setDeep(obj, ['foo','quux'], '😉');
console.log(obj);
// ⬇︎ Click "Run" below to see output
I was looking for an answer that does not overwrite existing values and was easily readable and was able to come up with this. Leaving this here in case it helps others with the same needs
function setValueAtObjectPath(obj, pathString, newValue) {
// create an array (pathComponents) of the period-separated path components from pathString
var pathComponents = pathString.split('.');
// create a object (tmpObj) that references the memory of obj
var tmpObj = obj;
for (var i = 0; i < pathComponents.length; i++) {
// if not on the last path component, then set the tmpObj as the value at this pathComponent
if (i !== pathComponents.length-1) {
// set tmpObj[pathComponents[i]] equal to an object of it's own value
tmpObj[pathComponents[i]] = {...tmpObj[pathComponents[i]]}
// set tmpObj to reference tmpObj[pathComponents[i]]
tmpObj = tmpObj[pathComponents[i]]
// else (IS the last path component), then set the value at this pathComponent equal to newValue
} else {
// set tmpObj[pathComponents[i]] equal to newValue
tmpObj[pathComponents[i]] = newValue
}
}
// return your object
return obj
}
Same as Rbar's answers, very useful when you're working with redux reducers. I use lodash clone instead of spread operator to support arrays too:
export function cloneAndPatch(obj, path, newValue, separator='.') {
let stack = Array.isArray(path) ? path : path.split(separator);
let newObj = _.clone(obj);
obj = newObj;
while (stack.length > 1) {
let property = stack.shift();
let sub = _.clone(obj[property]);
obj[property] = sub;
obj = sub;
}
obj[stack.shift()] = newValue;
return newObj;
}
Object.getPath = function(o, s) {
s = s.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
s = s.replace(/^\./, ''); // strip a leading dot
var a = s.split('.');
for (var i = 0, n = a.length; i < n; ++i) {
var k = a[i];
if (k in o) {
o = o[k];
} else {
return;
}
}
return o;
};
Object.setPath = function(o, p, v) {
var a = p.split('.');
var o = o;
for (var i = 0; i < a.length - 1; i++) {
if (a[i].indexOf('[') === -1) {
var n = a[i];
if (n in o) {
o = o[n];
} else {
o[n] = {};
o = o[n];
}
} else {
// Not totaly optimised
var ix = a[i].match(/\[.*?\]/g)[0];
var n = a[i].replace(ix, '');
o = o[n][ix.substr(1,ix.length-2)]
}
}
if (a[a.length - 1].indexOf('[') === -1) {
o[a[a.length - 1]] = v;
} else {
var ix = a[a.length - 1].match(/\[.*?\]/g)[0];
var n = a[a.length - 1].replace(ix, '');
o[n][ix.substr(1,ix.length-2)] = v;
}
};
Here's a simple method that uses a scoped Object that recursively set's the correct prop by path.
function setObjectValueByPath(pathScope, value, obj) {
const pathStrings = pathScope.split('/');
obj[pathStrings[0]] = pathStrings.length > 1 ?
setObjectValueByPath(
pathStrings.splice(1, pathStrings.length).join('/'),
value,
obj[pathStrings[0]]
) :
value;
return obj;
}
How about a simple and short one?
Object.assign(this.origin, { [propName]: value })
You can use reduce : (you can test it by copy/paste on browser console)
const setValueOf = (obj, value, ...path) => {
path.reduce((o, level, idx) => {
if(idx === path.length -1) { o[level] = value }; // on last change the value of the prop
return o && o[level]; // return the prop
}, obj);
};
Example
let objExmp = {a: 'a', b: {b1: 'b1', b2: 'b2', b3: { b3_3 : 'default_value' } }};
setValueOf(objExmp, 'new_value' , 'b', 'b3', 'b3_3');
console.log('objExmp', objExmp); // prop changed to 'new_value'
You can split the string path by '.' and spread like :
setValueOf(objExmp, 'new_value' , ...'b.b3.b3_3'.split('.'));
I have a potential stack overflow issue with a recursive function. Usually I can solve this with a while loop and a condition, but I cannot figure out the condition to based this while loop on.
Here is the current recursive function which counts the number of handlers in an object of unknown # of nested objects.
countHandlers(obj){
let count = 0;
for(let k in obj){
if(k === "_handlers"){
count += obj[k].length;
}
else if(typeof obj[k] === 'object') {
count += this.countHandlers(obj[k])
}
}
return count;
}
Can this be converted to a non-recursive function?
The way I usually get around recursive functions is to use a stack or a queue to maintain the data that needs to be processed.
Stacks are easier in JavaScript, so we'll go with that. :)
function countHandlers(obj) {
let stack = [];
stack.push(obj);
let count = 0;
while (stack.length > 0) {
let currentObj = stack.pop();
for (let k in currentObj) {
if (k === "_handlers") {
count += currentObj[k].length;
}
else if (typeof currentObj[k] === 'object') {
stack.push(currentObj[k]);
}
}
}
return count;
}
Problem arises in such recursive function when you have circular reference. You have to keep track of what objects you already parsed.
Let's say we have this object :
var test = {
_handlers: {
length: 1
},
child1: {
member1: {
_handlers: [7, 9, 12],
child: {
morehandlers: {
_handlers: {
length: 7
}
},
_handlers: [1]
}
},
member2: {
_handlers: {
length: 1
}
}
},
child2: {
value: 2
},
child3: {
last: {
_handlers: {
length: 7
}
}
}
}
Total handlers count should be 20.
And then we add a circular reference :
test.child1.member3 = test;
Here is how I would handle it without thinking of performances :
let parsedHandlers = null;
let handlersCountLaunched = false;
function countHandlers(obj) { // Cannot be async
let countObj = obj;
let count = 0;
for (let i = 0; i < parsedHandlers.length; i++) {
if (countObj === parsedHandlers[i]) {
countObj = null;
break;
}
}
if (countObj !== null) {
parsedHandlers.push(countObj);
for (let k in obj) {
if (k === "_handlers") {
count += obj[k].length;
} else if (typeof obj[k] === 'object') {
count += this.countHandlers(obj[k]);
}
}
}
return count;
}
function getHandlersCount(mainObj) {
if (!handlersCountLaunched) {
parsedHandlers = [];
handlersCountLaunched = true;
let count = countHandlers(mainObj);
handlersCountLaunched = false;
parsedHandlers = null;
return count;
} else {
console.error('TimingError : getHandlersCount() has been called and has not yet finished counting');
return -1;
}
}
console.log(getHandlersCount(test));
In javascript, unless you have setup a mapping logic, you can't retrive the parent object of a member. With a circular reference in the object, you will probably end up with the total amount of handlers in the object tree, unless you select a branch with no circular reference.
I have a requirement where I have an object like obj={ 'a.b.c' : d }
and I would like it to get converted to {a:{b:{c:d}}}
Is there any way I can achieve this in JavaScript?
Here's a solution (EDITED: code is more complex than before but it gives the result you want, let me know if something doesn't work):
var obj = {
'a.b.c': 22,
'a.b.d.e': 42
}
var newObj = {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
var keyList = key.split('.');
newObj = generateNewObject(keyList, keyList.length - 1, newObj, obj[key]);
}
}
console.log(newObj);
function generateNewObject(keys, index, existingObj, value) {
if (index < 0) {
return value;
}
var lastKey = keys[index--];
var existingProperty = getProperty(existingObj, lastKey);
if (existingProperty != null && !objectsAreEqual(existingProperty, value)) {
var valueKey = keys[index + 2];
existingProperty[lastKey][valueKey] = value[valueKey];
value = existingProperty;
} else {
var subObj = {};
subObj[lastKey] = value;
value = subObj;
}
return generateNewObject(keys, index, existingObj, value);
}
function objectsAreEqual(obj1, obj2) {
for (var key in obj1) {
if (obj1.hasOwnProperty(key)) {
var prop = getProperty(obj2, key);
if (prop == null) {
return false;
}
}
}
return true;
}
function getProperty(obj, keyDesired) {
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
if (key === keyDesired) {
return obj;
} else {
var prop = getProperty(obj[key], keyDesired);
if (prop != null) {
return prop;
}
}
}
}
return null;
}
I don't know why you would have an object named that way, but this code will do the trick for each key in an object. This will not work correctly on nested objects such as {'a' : { 'b' { 'c' : {{'d' : 'e'}}}}}. You would have to repeat the for-loop part each time the value is a JavaScript object.
EDIT
I modified the code so it recognizes when two properties are the same such as the example { 'a.b.c' : 22 }, 'a.b.c.d.e' : 42. Sorry if it is hard to go through, but basically the generateNewObject method is the real meat of it. The two functions below it are just helper methods.
Array.reduce mostly is a good choice when it comes to handling/transforming of more complex data structures. An approach that solves the given problem generically whilst taking edge cases into account then might look similar to the next provided example ...
var
d = 'd',
q = 'q',
obj = {
'i.k.l.m.n.o.p' : q,
'a.b.c' : d,
'foo' : 'bar',
'' : 'empty'
};
function parseIntoNestedTypes(type) {
return Object.keys(type).reduce(function (collector, integralKey) {
var
nestedType = collector.target,
fragmentedKeyList = integralKey.split('.'),
nestedTypeRootKey = fragmentedKeyList.shift(),
nestedTypeEndValue = collector.source[integralKey];
if (fragmentedKeyList.length === 0) {
nestedType[nestedTypeRootKey] = nestedTypeEndValue;
} else {
nestedType[nestedTypeRootKey] = fragmentedKeyList.reduce(function (collector, key, idx, list) {
var
partialType = collector.partialType || collector.type;
if (idx < (list.length - 1)) {
partialType[key] = {};
} else {
partialType[key] = collector.value;
}
collector.partialType = partialType[key];
return collector;
}, {
value : nestedTypeEndValue,
type : {}
}).type;
}
return collector;
}, {
source: type,
target: {}
}).target;
}
console.log('parseIntoNestedTypes :: type', JSON.stringify(obj));
console.log('parseIntoNestedTypes :: nestedType', JSON.stringify(parseIntoNestedTypes(obj)));
console.log('parseIntoNestedTypes :: type, nestedType : ', obj, parseIntoNestedTypes(obj));
var obj = {
people: {
John: {
pets:{
dog:{
name:"Umbrella",
age:12
},
cat:{
name:"Kitty",
age:5
}
}
}
}
};
var path=['people', 'John', 'pets', 'cat', 'name'];
var newName='Train';
How do I SET what the array wants (now it is cat's name) to the object? The array can be changed, so I can not write it by hand. I need a function to do it automatically depending on the array
You can use a recursive function like this one:
var prop = function (obj, chain) {
if (obj && chain.length > 0) {
return prop(obj[chain[0]], chain.slice(1));
}
return obj;
};
prop(obj, path);
Or even an iterative function:
var prop = function (obj, chain) {
var i = 0;
while (obj && i < chain.length) {
obj = obj[chain[i]];
i++;
}
return obj;
};
prop(obj, path);
EDIT: To set a value, you can do something like this:
var set = function (obj, chain, value) {
if (obj === undefined || obj === null) return;
var i = 0;
while (obj[chain[i]] !== undefined && obj[chain[0]] !== null && i < chain.length - 1) {
obj = obj[chain[i]];
i++;
}
obj[chain[i]] = value;
};
set(obj, path, 'Train');
use the below function recursively
foreach(var key in obj)
{
console.log('key'); //gives the key name like - people, John, Pets, dog, cat etc.,
}
Using for loop:
var cursor = obj;
for (var i = 0, len = path.length; i < len; i++) {
cursor = cursor[path[i]];
}
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.