Сравнение Javascript - deepEqual
Вопрос (от Eloquent Javascript 2nd Edition, глава 4, упражнение 4):
Напишите функцию deepEqual, которая принимает два значения и возвращает true, только если они являются одним и тем же значением или являются объектами с теми же свойствами, значения которых также равный по сравнению с рекурсивным вызовом deepEqual.
Тестовые случаи:
var obj = {here: {is: "an"}, 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}));
// → true
Мой код:
var deepEqual = function (x, y) {
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))
return deepEqual(x[prop], y[prop]);
/*This is most likely where my error is. The question states that all the values
should be checked via recursion; however, with the current setup, only the first
set of properties will be checked. It passes the test cases, but I would like
to solve the problem correctly!*/
}
}
else if (x !== y)
return false;
else
return true;
}
Я думаю, что у меня есть общая идея; однако, как я уже сказал в комментарии, программа не проверит второе свойство в объектах. Я чувствую, что у меня есть структурно-логическая проблема, и я просто использую рекурсию не так, как я изначально собирался перебирать свойства, использовать рекурсию для сравнения значений первого свойства, а затем продолжить в цикле до следующего собственности и сравнить снова. Хотя, я не уверен, что это возможно?
Я дал много размышлений и попробовал несколько разных подходов, но это был самый правильный ответ, который я до сих пор пришел. Любые возможные подсказки, чтобы указать мне в правильном направлении?
Ответы
Ответ 1
Как вы подозреваете, вы возвращаете совпадение первого свойства. Вы должны вернуть false
, если это свойство не соответствует, но продолжать искать иначе.
Кроме того, верните false
, если нет свойства prop
, найденного на y
(то есть совпадение совпадений, но не фактические свойства).
Если все свойства совпали, верните 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
Ответ 2
Почувствуйте, что эта версия немного более читабельна (легче понять). Логика очень похожа на верхний ответ, хотя. (ES6 на этот раз)
function deepEqual(obj1, obj2) {
if(obj1 === obj2) // it just the same object. No need to compare.
return true;
if(isPrimitive(obj1) && isPrimitive(obj2)) // compare primitives
return obj1 === obj2;
if(Object.keys(obj1).length !== Object.keys(obj2).length)
return false;
// compare objects with same number of keys
for(let key in obj1)
{
if(!(key in obj2)) return false; //other object doesn't have this prop
if(!deepEqual(obj1[key], obj2[key])) return false;
}
return true;
}
//check if value is primitive
function isPrimitive(obj)
{
return (obj !== Object(obj));
}
Кстати, есть читерская версия глубокого равенства, которая работает как шарм)) Однако она примерно в 1,6 раза медленнее.
Как заметил ноль298, этот подход чувствителен к упорядочению свойств и не должен восприниматься всерьез
function cheatDeepEqual(obj1, obj2)
{
return JSON.stringify(obj1) === JSON.stringify(obj2);
}
Ответ 3
Вы можете использовать переменную вне цикла for, чтобы отслеживать сравнение:
var allPropertiesEqual = true;
for (var prop in x) {
if (y.hasOwnProperty(prop)) {
allPropertiesEqual = deepEqual(x[prop], y[prop]) && allPropertiesEqual;
} else {
allPropertiesEqual = false;
}
}
return allPropertiesEqual;
Предыдущий пример не оптимизирован специально. Поскольку вы сравниваете объекты, вы знаете, что можете return false
, как только найдете неравенство, и вы можете продолжать цикл, пока все предыдущие проверенные свойства равны:
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;
Ответ 4
Я новичок в JS, но так я решил это:
function deepEqual(obj1, obj2) {
if (typeof obj1 === "object" && typeof obj2 === "object") {
let isObjectMatch = false;
for (let property1 in obj1) {
let isPropertyMatch = false;
for (let property2 in obj2) {
if (property1 === property2) {
isPropertyMatch = deepEqual(obj1[property1], obj2[property2])
}
if(isPropertyMatch){
break;
}
}
isObjectMatch = isPropertyMatch;
if (!isObjectMatch) {
break;
}
}
return isObjectMatch;
} else {
return obj1 === obj2;
}
}
И вот мои тесты:
var obj = {here: {is: "an"}, 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}))
// → true
console.log(deepEqual(obj, {object: 2, here: {is: "an"}}));
// → true
console.log(deepEqual(obj, {object: 1, here: {is: "an"}}));
// → false
console.log(deepEqual(obj, {objectt: 2, here: {is: "an"}}));
// → false
console.log(deepEqual(2, 2));
// → true
console.log(deepEqual(2, 3));
// → false
console.log(deepEqual(2, null));
// → false
console.log(deepEqual(null, null));
// → false
console.log(deepEqual(obj, null));
// → false
Ответ 5
Хотя это более многословно, возможно, этот вариант легче читать:
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;
}
Ответ 6
<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>
Ответ 7
Основываясь на принятом ответе Пола Руба, я нуждался в том, чтобы он также совпадал со значениями функций, и я хотел, чтобы он был намного более кратким, поэтому я реорганизовал его.
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
Ответ 8
Я просто прошел эту главу и хотел показать свою работу тоже.
Недостаток в моей (дайте мне знать, если есть больше) заключается в том, что свойства объекта должны быть в точном порядке. Я предпочитаю решение @paul и @danni.
// 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>