recursive isEqual function causes Chrome freeze - javascript

I have 2 large objects to compare. I want to know if they are equal.
JSON.stringify(obj1) == JSON.stringify(obj2) does not work, because the objects are dynamically created so the order of the attributes are random.
So I wrote a isEqual() as follows.
function isEqual(ar1, ar2) {
if(ar1 !== ar2) {
if(typeof ar1 !== typeof ar2) {
return false;
}
if(ar1 == null) {
if(ar2 == null) {
return true;
}
return false;
}
if(ar2 == null) {
if(ar1 == null) {
return true;
}
return false;
}
if(typeof ar1 !== 'object') {
return false;
}
if (ar1.length !== ar2.length) {
return false;
}
for(var i in ar1) {
if(!isEqual(ar1[i], ar2[i])) {
return false;
}
}
for(var i in ar2) {
if(!isEqual(ar1[i], ar2[i])) {
return false;
}
}
}
return true;
}
Now if I run isEqual(obj1, obj2) the tab in chrome freezes and I am not able to close the tab. I have to wait until chrome asks me to close non responding tabs after about 10 minutes. How to solve this?

Use Lodash's isEqual to do this for you.
https://lodash.com/docs/4.17.4#isEqual

I'd like to offer my solution. Please try it with your JSON objects.
in order to check if objects have a different number of fields you can use
Object.keys(ar1).length !== Object.keys(ar2).length
function isEqual(ar1, ar2) {
if((ar1 === null) && (ar2 === null))
return true;
//console.info("Type: " + typeof ar1 + ":" + typeof ar2)
if (typeof ar1 !== typeof ar2) {
return false;
}
console.info("Length: " + ar1.length + ":" + ar2.length)
if (ar1.length !== ar2.length) {
return false;
}
if((typeof ar1 === 'object') && (typeof ar2 === 'object') && (Object.keys(ar1).length !== Object.keys(ar2).length))
return false;
if ((typeof ar1 !== 'object') && (typeof ar2 !== 'object')) {
//console.info("Value is not equal: " + (ar1 !== ar2))
if (ar1 !== ar2)
return false;
else
return true;
}
for (var i in ar1) {
//console.info("Values:" + ar1[i] + ":" + ar2[i])
if (!isEqual(ar1[i], ar2[i])) {
return false;
}
}
return true;
}
var obj1 = {
a : 1,
b :[1, 2, 3],
c: {a:1}
};
var obj2 = {
a : 1,
b : [1, 2, 3],
c: {a:1},
d:1
};
isEqual(obj1, obj2)

Related

A question about freeCodeCamp Challenge : Arguments Optional

I'm stuck with one of curriculum in freeCodeCamp.org
https://www.freecodecamp.org/learn/javascript-algorithms-and-data-structures/intermediate-algorithm-scripting/arguments-optional
The below code is what I wrote.
In that code, addTogether(2)(3) should be 5.
But instead of that, addTogether(2)(3) is "undefined"
what's the problem?
I read all hints from freecodecamp forum. But I don't get it.
function addTogether() {
var checkNum = function(x) {
if (typeof x === "number") {
return x
} else {
return undefined
}
}
if (arguments.length > 1) {
if (checkNum(arguments[0]) !== undefined && checkNum(arguments[1]) !== undefined) {
return arguments[0] + arguments[1]
} else {
return undefined
}
} else {
var a = arguments[0]
if (checkNum(a) === undefined) {
return undefined
} else {
return function(args2) {
args2 + a
}
}
}
return false;
}
console.log(addTogether(2)(3))
Your returned function doesn't have a return value. You could use
return function(args2) {
return args2 + a
}
Or
return (args2) => args2 + a
function addTogether(a,b) {
if (arguments.length==2){
if (typeof a == "number" && typeof b == "number"){
return a + b;
}
}
if(arguments.length==1){
if (typeof a == "number"){
return function(b){
if (typeof b == "number"){
return a + b;
}
};
}
}
}

How to count number of if/for/while/switch/try statements using esprima

I want to display files that have more then 3 if/for/while/switch/try statements (same as sonar) so far I have code that traverse the tree and count statements for whole file:
var fs = require('fs');
var esprima = require('esprima');
function traverse(obj, fn) {
for (var key in obj) {
if (obj[key] !== null && fn(obj[key]) === false) {
return false;
}
if (typeof obj[key] == 'object' && obj[key] !== null) {
if (traverse(obj[key], fn) === false) {
return false;
}
}
}
}
fs.readFile(process.argv[2], function(err, file) {
var syntax = esprima.parse(file.toString());
var count = 0;
traverse(syntax, function(obj) {
if (obj.type == "TryStatement" || obj.type == "ForStatement" ||
obj.type == "IfStatement" || obj.type == "WhileStatement"
obj.type == "SwitchStatement") {
count++;
}
});
console.log(process.argv[2] + ' ' + count);
});
How can I count path in the program for instance for this code:
try {
if (foo == 10) {
for (var i=0; i<foo; ++i) {
if (i % 5 == 0) {
var j = i;
while(j--) {
console.log(j);
}
}
}
}
} catch(e) {
if (e instanceof Error) {
if (e.stack) {
console.log(e.stack);
}
}
}
it should show 5 and 2 not 7.

How can I create empty JSON keys (nested or not) using a string?

I currently have this code built in JS, but it's really, really ugly.
Is there any better way to approach it?
The way it works basically is pushing a string like app.chat.test to be the key, and value like teststr.
I test the lengths to see if the "parent" key is there, otherwise we build it.
function constructJson(jsonKey, jsonValue){
//REWRITE!!!!!!!!
let jsonObj = langFile;
let jsonKeyArr = jsonKey.split('.')
if (jsonKeyArr.length === 1) {
if (valToAdd === undefined) {
if (jsonObj[jsonKey] === undefined) {
jsonObj[jsonKey] = {}
}
} else {
if (jsonObj[jsonKey] === undefined) {
jsonObj[jsonKey] = valToAdd
}
}
} else if (jsonKeyArr.length === 2) {
if (jsonObj[jsonKeyArr[0]] === undefined) {
jsonObj[jsonKeyArr[0]] = {}
}
if (jsonObj[jsonKeyArr[0]][jsonKeyArr[1]] === undefined) {
jsonObj[jsonKeyArr[0]][jsonKeyArr[1]] = jsonValue
}
} else if (jsonKeyArr.length === 3) {
if (jsonObj[jsonKeyArr[0]] === undefined) {
jsonObj[jsonKeyArr[0]] = {}
}
if (jsonObj[jsonKeyArr[0]][jsonKeyArr[1]] === undefined) {
jsonObj[jsonKeyArr[0]][jsonKeyArr[1]] = {}
}
if (jsonObj[jsonKeyArr[0]][jsonKeyArr[1]][jsonKeyArr[2]] === undefined) {
jsonObj[jsonKeyArr[0]][jsonKeyArr[1]][jsonKeyArr[2]] = jsonValue
}
} else if (jsonKeyArr.length === 4) {
if (jsonObj[jsonKeyArr[0]] === undefined) {
jsonObj[jsonKeyArr[0]] = {}
}
if (jsonObj[jsonKeyArr[0]][jsonKeyArr[1]] === undefined) {
jsonObj[jsonKeyArr[0]][jsonKeyArr[1]] = {}
}
if (jsonObj[jsonKeyArr[0]][jsonKeyArr[1]][jsonKeyArr[2]] === undefined) {
jsonObj[jsonKeyArr[0]][jsonKeyArr[1]][jsonKeyArr[2]] = {}
}
if (jsonObj[jsonKeyArr[0]][jsonKeyArr[1]][jsonKeyArr[2]][jsonKeyArr[3]] === undefined) {
jsonObj[jsonKeyArr[0]][jsonKeyArr[1]][jsonKeyArr[2]][jsonKeyArr[3]] = jsonValue
}
} else if (jsonKeyArr.length === 5) {
if (jsonObj[jsonKeyArr[0]] === undefined) {
jsonObj[jsonKeyArr[0]] = {}
}
if (jsonObj[jsonKeyArr[0]][jsonKeyArr[1]] === undefined) {
jsonObj[jsonKeyArr[0]][jsonKeyArr[1]] = {}
}
if (jsonObj[jsonKeyArr[0]][jsonKeyArr[1]][jsonKeyArr[2]] === undefined) {
jsonObj[jsonKeyArr[0]][jsonKeyArr[1]][jsonKeyArr[2]] = {}
}
if (jsonObj[jsonKeyArr[0]][jsonKeyArr[1]][jsonKeyArr[2]][jsonKeyArr[3]] === undefined) {
jsonObj[jsonKeyArr[0]][jsonKeyArr[1]][jsonKeyArr[2]][jsonKeyArr[3]] = {}
}
if (jsonObj[jsonKeyArr[0]][jsonKeyArr[1]][jsonKeyArr[2]][jsonKeyArr[3]][jsonKeyArr[4]] === undefined) {
jsonObj[jsonKeyArr[0]][jsonKeyArr[1]][jsonKeyArr[2]][jsonKeyArr[3]][jsonKeyArr[4]] = jsonValue
}
} else if (jsonKeyArr.length > 5) {
return console.log("Length over 5 not supported yet!")
}
return jsonObj;
}
Regards.
OF course it's possible, a simple loop will perfeclty do the job.
function constructJson(jsonKey, jsonValue){
//REWRITE!!!!!!!!
langFile = {a:{}, foo:{}};// remove this for your own code
var jsonObj = langFile;
var jsonKeyArr = jsonKey.split('.');
var currentValue = jsonObj;
for(var i = 0; i < jsonKeyArr.length;i++){
if(currentValue[jsonKeyArr[i]]===undefined){
currentValue[jsonKeyArr[i]] = {};
}
if(i < jsonKeyArr.length-1){
currentValue = currentValue[jsonKeyArr[i]];
}else{
currentValue[jsonKeyArr[i]] = jsonValue;
}
}
return jsonObj;
}
alert(JSON.stringify(constructJson("a.b.cd.ef", "toto")));
I just assigning to a temporary variable each sublevel. When i'm on the last i'm assigning the value.
Yes you can, using the javascript reduce function on the array created from the splitted string.
function namespaceCreateExceptLast(representationOfElementToCreate, baseNamespace) {
var tokens;
if (typeof representationOfElementToCreate !== 'string')
throw new Error('Expecting string as first parameter');
if (baseNamespace === undefined)
baseNamespace = window;
tokens = representationOfElementToCreate.split('.');
// Remove the last element (which will contain the value)
tokens.pop();
// Use reduce to create part by part your new object
return tokens.reduce(function (prev, property) {
if (typeof prev !== 'object') {
throw Error('One property is already defined but not an object, namespace creation has failed', property);
return undefined;
} else {
if (!prev[property])
prev[property] = {};
return prev[property];
}
}, baseNamespace);
};
Then you can have:
function constructJson(jsonKey, jsonValue){
let jsonObj = langFile;
var lastItem = namespaceCreateExceptLast(jsonKey, jsonObj);
var lastKey = jsonKey.substring(jsonKey.lastIndexOf('.') + 1);
lastItem[lastKey] = jsonValue;
}
I have added some comments and exceptions to help you understand how it's done, but it's mainly based on the reduce function which you can easily get help for (https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Array/reduce).

Code works with arrays, but not multidimensional arrays.

Working on a Holdem hand evaluator, and part of the yak shaving is writing a "how many combos of 5 do you get from 7 cards" function (pickNofSet()). I've done that, but the way I've done that returns a bunch of duplicates.
So I have to write a removeDuplicates(). Here's the problem... it works with a simple array, but it doesn't work with the "arrays of arrays" that my "pickNofSet" function generates.
-- here's the removeDuplicates code --
var removeDuplicates = function(input){ // takes array
var output = [];
for (i=0; i < input.length; i++){
var unique = true; // all elements are innocent until proven guilty
for(j=i+1; j < input.length; j++){
if(input[j] === input[i]){
unique = false; // guilty!
};// endif
};// end jfor
if(unique){ // if not found guilty,
output.push(input[i]); // you may go free, little element
};// end if
};// end ifor
console.log(output);
return output; };//end function
Here's what I get from the Console:
> removeDuplicates(['a','b','c'],['a','b','c'],['d','e','f'],['g','h','i']);
< undefined
> removeDuplicates([1, 2, 2, 3, 3, 5, 5, 6, 6, 6]);
< [1, 2, 3, 5, 6]
Operator === can't compare array
As you use === to check equality between two elements, this only works with primitive data types, but not array. e.g.
1===1 // true
[1]===[1] // Sorry, you can't
If you want your algorithm to work with both primitive elements and array elements, you may need to upgrade your equality check from === to a customized function.
From this:
if(input[j] === input[i]){
unique = false; // guilty!
};// endif
To this:
if (equals(input[j],input[i]){
unique = false; // guilty!
};// endif
And implement equals function to be capable of comparing both primitive data types and arrays:
function equals(a,b){
if (typeof(a)!=typeof(b))
return false;
else if (typeof(a)=='object'){
if (Object.keys(a).length != Object.keys(b).length)
return false;
else{
for (var keyA of Object.keys(a)){
if (!(keyA in b))
return false;
else if (a[keyA]!==b[keyA])
return false;
else
return true;
}
}
}
else
return a===b;
}
Hint: This solution should, hopefully, also work with JSON element.
Here is an example of a general purpose unique filter that should fill your need. Requires an ES5 compliant environment.
(function () {
'use strict';
function $strictEqual(a, b) {
return a === b;
}
function $isUndefined(inputArg) {
return $strictEqual(typeof inputArg, 'undefined');
}
function $isPrimitive(inputArg) {
var type = typeof inputArg;
return type === 'undefined' || inputArg === null || type === 'boolean' || type === 'string' || type === 'number' || type === 'symbol';
}
function $isFunction(inputArg) {
return typeof inputArg === 'function';
}
function $isDate(inputArg) {
return Object.prototype.toString.call(inputArg) === '[object Date]';
}
function $isRegExp(inputArg) {
return Object.prototype.toString.call(inputArg) === '[object RegExp]';
}
function $isString(inputArg) {
return Object.prototype.toString.call(inputArg) === '[object String]';
}
function $isArguments(inputArg) {
return Object.prototype.toString.call(inputArg) === '[object Arguments]';
}
function $getItem(object, index) {
var item;
if ($isString(object)) {
item = object.charAt(index);
} else {
item = object[index];
}
return item;
}
var de = function (a, b, circ) {
if (a === b) {
return true;
}
var aType,
bType,
aIsArgs,
bIsArgs,
aIsPrim,
bIsPrim,
aCirc,
bCirc,
ka,
kb,
length,
index,
it;
if ($isDate(a) && $isDate(b)) {
return a.getTime() === b.getTime();
}
if ($isRegExp(a) && $isRegExp(b)) {
return a.source === b.source &&
a.global === b.global &&
a.multiline === b.multiline &&
a.lastIndex === b.lastIndex &&
a.ignoreCase === b.ignoreCase &&
a.sticky === b.sticky;
}
aIsPrim = $isPrimitive(a);
bIsPrim = $isPrimitive(b);
if ((aIsPrim || $isFunction(a)) && (bIsPrim || $isFunction(b))) {
/*jslint eqeq: true */
return a == b;
}
if (aIsPrim || bIsPrim) {
return a === b;
}
if (a.prototype !== b.prototype) {
return false;
}
if (circ.a.indexOf(a) === -1) {
circ.a.push(a);
} else {
aCirc = true;
}
if (circ.b.indexOf(b) === -1) {
circ.b.push(b);
} else {
bCirc = true;
}
if (aCirc && bCirc) {
circ.cnt += 1;
} else {
circ.cnt = 0;
}
if (circ.cnt > 200) {
throw new RangeError('Circular reference limit exceeded');
}
aIsArgs = $isArguments(a);
bIsArgs = $isArguments(b);
if ((aIsArgs && !bIsArgs) || (!aIsArgs && bIsArgs)) {
return false;
}
if (aIsArgs) {
return de(Array.prototype.slice.call(a), Array.prototype.slice.call(b), circ);
}
ka = Object.keys(a);
kb = Object.keys(b);
length = ka.length;
if (length !== kb.length) {
if (Array.isArray(a) && Array.isArray(b)) {
if (a.length !== b.length) {
return false;
}
} else {
return false;
}
} else {
ka.sort();
kb.sort();
for (index = 0; index < length; index += 1) {
if (ka[index] !== kb[index]) {
return false;
}
}
}
for (index = 0; index < length; index += 1) {
it = ka[index];
if (!de($getItem(a, it), $getItem(b, it), circ)) {
return false;
}
}
aType = typeof a;
bType = typeof b;
return aType === bType;
};
if (!Object.prototype.deepEqual) {
Object.defineProperty(Object.prototype, 'deepEqual', {
enumerable: false,
configurable: true,
writable: true,
value: function (b) {
var a = this;
return de(a, b, {
a: [],
b: [],
cnt: 0
});
}
});
}
if (!Array.prototype.unique) {
Object.defineProperty(Array.prototype, 'unique', {
enumerable: false,
configurable: true,
writable: true,
value: function (equalFn, thisArg) {
var object = Object(this),
length,
index,
eqFn,
arr,
idx,
val,
it;
if ($isUndefined(equalFn)) {
eqFn = $strictEqual;
} else {
eqFn = equalFn;
}
if (!$isFunction(eqFn)) {
throw new TypeError('Argument is not a function: ' + eqFn);
}
arr = [];
length = object.length >>> 0;
for (index = 0; index < length; index += 1) {
if (index in object) {
it = $getItem(object, index);
val = true;
for (idx = 0; idx < length; idx += 1) {
if (idx < index && idx in object && eqFn.call(thisArg, it, $getItem(object, idx))) {
val = false;
break;
}
}
if (val) {
arr.push(it);
}
}
}
return arr;
}
});
}
}());
var data1 = [1, 2, 2, 3, 3, 5, 5, 6, 6, 6],
data2 = [
['a', 'b', 'c'],
['a', 'b', 'c'],
['d', 'e', 'f'],
['g', 'h', 'i']
],
equals = Function.prototype.call.bind(Object.prototype.deepEqual),
pre = document.getElementById('out');
pre.textContent = JSON.stringify(data1.unique(equals), null, 2);
pre.textContent += '\n\n';
pre.textContent += JSON.stringify(data2.unique(equals), null, 2);
<pre id="out"></pre>

Recursive method for checking an object

So I have created a constructor that I am attempting to prototype. I want a method that checks through each property in an object to see if its empty and if it is it returns the key. If the property is an object I want it to check through that sub object as well.
UPDATED:
My code thus far:
function Properties(val1, val2, val3, val4){
this.prop1 = val1 || "";
this.prop2 = val2 || "";
this.prop3 = val3 || "";
this.prop4 = val4 || {};
}
Properties.prototype = {
isEmpty: function(){
for (key in this) {
if(typeof this[key] == "object" && this[key] !== null){
this[key].isEmpty();
} else {
if(!this[key]){
console.log(key);
}
}
}
}
}
var test = new Properties("Something", "", "", {subProp1: "Something Else", subProp2: "", subProp3: {subSubProp1: "", subSubProp2: "" }});
The method should return prop2, prop3, subProp2, subSubProp1, subSubProp2
That method isn't a property on the object. You need to pass in the object in question. You can also pass an array in to keep track of the empty keys:
var emptyKeys = [];
function isEmpty(obj, keysArr) {
for (var key in obj) {
if (typeof obj[key] === "object" && obj.hasOwnProperty(key)) {
isEmpty(obj[key], keysArr);
} else {
if (obj[key] == "" || obj[key] == null) {
keysArr.push(key);
}
}
}
}
Demo: http://jsfiddle.net/17rt0qy3/1/
If you want this on the actual object, simply add the above function inside the isEmpty function:
isEmpty: function(){
var emptyKeys = [];
amIEmpty(this, emptyKeys);
return emptyKeys;
//Actual logic
function amIEmpty(obj, keysArr) {
for (var key in obj) {
if (key == "isEmpty") {
continue;
}
if (typeof obj[key] === "object" && obj.hasOwnProperty(key)) {
amIEmpty(obj[key], keysArr);
} else {
if (obj[key] == "" || obj[key] == null) {
keysArr.push(key);
}
}
}
}
}
Demo: http://jsfiddle.net/17rt0qy3/2/
And a fiddle working with your demo object above: http://jsfiddle.net/17rt0qy3/3/
Aaand another edit, this will only log the keys, but it's a bit cleaner:
isEmpty: function(obj, keys) {
keys = keys || [];
obj = obj || this;
for (var key in obj) {
if (typeof obj[key] === "object" && obj.hasOwnProperty(key)) {
this.isEmpty(obj[key], keys)
} else {
if (obj[key] == "" || obj[key] == null) {
keys.push(key);
}
}
}
return keys;
}
Demo: http://jsfiddle.net/17rt0qy3/8/

Categories