Question (From Eloquent Javascript 2nd Edition, Chapter 4, Exercise 4):
Write a function, deepEqual, that takes two values and returns true only if th
Although that it's more verbose, maybe this option is easier to read:
function deepEqual(elem1, elem2) {
if(elem1 === elem2) {
return true;
}
if(typeof elem1 == 'object' && typeof elem2 == 'object' && elem1 != null && elem2 != null) {
if(Object.keys(elem1).length == Object.keys(elem2).length) {
for(let key of Object.keys(elem1)) {
if(elem2.hasOwnProperty(key) != true) {
return false;
}
}
for(let key of Object.keys(elem1)) {
if(typeof elem1[key] == 'object' && typeof elem2[key] == 'object' && typeof elem1[key] != null && typeof elem2[key] != null) {
return deepEqual(elem1[key], elem2[key]);
}
else {
if(elem1[key] !== elem2[key]) {
return false;
}
}
} else {
return false;
}
}
}
else {
return false;
}
return true;
}
Based on the accepted answer by Paul Roub, I needed it to also match function values, and I wanted it to be a lot more concise, so I've refactored it.
function deepEqual(x, y, z) {
return x === y || typeof x == "function" && y && x.toString() == y.toString()
|| x && y && typeof x == "object" && x.constructor == y.constructor
&& (z = Object.keys(y)) && z.length == Object.keys(x).length
&& !z.find(v => !deepEqual(x[v], y[v]));
}
var myFunc = (x) => { return x*2; }
var obj = {here: {is: "an", other: "3"}, object: 2, andFunc: myFunc};
console.log(deepEqual(obj, obj));
// → true
console.log(deepEqual(obj, {here: 1, object: 2, andFunc: myFunc}));
// → false
console.log(deepEqual(obj, {here: {is: "an"}, object: 2, andFunc: myFunc}));
// → false
console.log(deepEqual(obj, {here: {is: "an", other: "2"}, object: 2, andFunc: myFunc}));
// → false
console.log(deepEqual(obj, {here: {is: "an", other: "3"}, object: 2, andFunc: myFunc}));
// → true
console.log(deepEqual(obj, {here: {is: "an", other: "3"}, object: 2, andFunc: (x) => { return x*2; }}));
// → true
console.log(deepEqual(obj, {here: {is: "an", other: "3"}, object: 2, andFunc: (x) => { return x*999; }}));
// → false
notes:
null
or undefined
it returns that value instead of false
, but that result is still "falsey" so I'm OK with it. To fix that you could change all occurrences of y &&
to (y || !1) &&
and x &&
to (x || !1) &&
|| typeof x == "function" && y && x.toString() == y.toString()
I just went through this chapter and wanted to show my work, too.
The flaw in mine (let me know if there are more) is that the object properties have to be in exact order as well. I much prefer @paul and @danni's solution.
// Deep equal
const deepEqual = (x, y) => {
const xType = typeof x;
const yType = typeof y;
if ( xType === 'object' && yType === 'object' && ( x !== null && y !== null ) ) {
const xKeys = Object.keys(x);
const yKeys = Object.keys(y);
const xValues = Object.values(x);
const yValues = Object.values(y);
// check length of both arrays
if ( xKeys.length !== yKeys.length ) return false;
// compare keys
for ( i = 0; i < xKeys.length; i++ )
if (xKeys[i] !== yKeys[i]) return false;
// compare values
for ( i = 0; i < xValues.length; i++ )
if (!deepEqual(xValues[i], yValues[i])) return false;
} else {
if ( x !== y ) return false;
}
return true;
};
// Objects
let obj1 = {
value: false,
pets: null
};
let obj2 = {
value: false,
pets: null
};
let obj3 = {
value: false,
pets: {
cat: false,
dog: {
better: 'yes'
}
}
};
let obj4 = {
value: false,
pets: {
cat: false,
dog: {
better: 'yes'
}
}
};
let obj5 = {
value: false,
dog: true
};
let obj6 = {
value: false,
cat: true
};
let obj7 = {
value: true,
dog: {
cat: {
wow: true
}
}
};
let obj8 = {
value: true,
dog: {
cat: {
wow: false
}
}
};
let obj9 = {
value: true,
dog: {
cat: {
wow: true
}
}
};
let obj10 = {
dog: {
cat: {
wow: true
}
},
value: true
};
// Just for building a pretty result, ignore if you'd like
const result = (x, y) => {
return `For: <br/>
${JSON.stringify(x)} <br/>
and <br/>
${JSON.stringify(y)} <br/>
<span>>> ${deepEqual(x, y)}</span>`;
};
// To print results in
const resultDivs = document.querySelectorAll('.result');
resultDivs[0].innerHTML = result(obj1, obj2);
resultDivs[1].innerHTML = result(obj3, obj4);
resultDivs[2].innerHTML = result(obj5, obj6);
resultDivs[3].innerHTML = result(obj7, obj8);
resultDivs[4].innerHTML = result(obj9, obj10);
body {
font-family: monospace;
}
span {
color: #a0a0a0;
}
.result {
margin-bottom: 1em;
}
<div class="result">
</div>
<div class="result">
</div>
<div class="result">
</div>
<div class="result">
</div>
<div class="result">
</div>
As you suspect, you're returning the match of the first property seen. You should return false
if that property doesn't match, but keep looking otherwise.
Also, return false
if there's no prop
property found on y
(that is, the counts match, but not the actual properties).
If all properties have matched, return true
:
var deepEqual = function (x, y) {
if (x === y) {
return true;
}
else if ((typeof x == "object" && x != null) && (typeof y == "object" && y != null)) {
if (Object.keys(x).length != Object.keys(y).length)
return false;
for (var prop in x) {
if (y.hasOwnProperty(prop))
{
if (! deepEqual(x[prop], y[prop]))
return false;
}
else
return false;
}
return true;
}
else
return false;
}
var deepEqual = function (x, y) {
if (x === y) {
return true;
}
else if ((typeof x == "object" && x != null) && (typeof y == "object" && y != null)) {
if (Object.keys(x).length != Object.keys(y).length)
return false;
for (var prop in x) {
if (y.hasOwnProperty(prop))
{
if (! deepEqual(x[prop], y[prop]))
return false;
}
else
return false;
}
return true;
}
else
return false;
}
var obj = {here: {is: "an", other: "3"}, object: 2};
console.log(deepEqual(obj, obj));
// → true
console.log(deepEqual(obj, {here: 1, object: 2}));
// → false
console.log(deepEqual(obj, {here: {is: "an"}, object: 2}));
// → false
console.log(deepEqual(obj, {here: {is: "an", other: "2"}, object: 2}));
// → false
console.log(deepEqual(obj, {here: {is: "an", other: "3"}, object: 2}));
// → true
You can use a variable outside the for loop to keep track of the comparison:
var allPropertiesEqual = true;
for (var prop in x) {
if (y.hasOwnProperty(prop)) {
allPropertiesEqual = deepEqual(x[prop], y[prop]) && allPropertiesEqual;
} else {
allPropertiesEqual = false;
}
}
return allPropertiesEqual;
The previous example is not optimized on purpose. Because you're comparing objects, you know that you can return false
as soon as you find an inequality, and you can keep looping while all the previous checked properties are equal:
for (var prop in x) {
if (y.hasOwnProperty(prop)) {
if (! deepEqual(x[prop], y[prop]) )
return false; //first inequality found, return false
} else {
return false; //different properties, so inequality, so return false
}
}
return true;
<script>
var cmp = function(element, target){
if(typeof element !== typeof target)
{
return false;
}
else if(typeof element === "object" && (!target || !element))
{
return target === element;
}
else if(typeof element === "object")
{
var keys_element = Object.keys(element);
var keys_target = Object.keys(target);
if(keys_element.length !== keys_target.length)
{
return false;
}
else
{
for(var i = 0; i < keys_element.length; i++)
{
if(keys_element[i] !== keys_target[i])
return false;
if(!cmp(element[keys_element[i]], target[keys_target[i]]))
return false;
}
return true;
}
}
else
{
return element === target;
}
};
console.log(cmp({
key1: 3,
key2: "string",
key3: [4, "45", {key4: [5, "6", false, null, {v:1}]}]
}, {
key1: 3,
key2: "string",
key3: [4, "45", {key4: [5, "6", false, null, {v:1}]}]
})); // true
console.log(cmp({
key1: 3,
key2: "string",
key3: [4, "45", {key4: [5, "6", false, null, {v:1}]}]
}, {
key1: 3,
key2: "string",
key3: [4, "45", {key4: [5, "6", undefined, null, {v:1}]}]
})); // false
</script>