使用JavaScript对象的共享*测试深度相等性*
在测试两个对象在JavaScript中的深度相等性这一主题上,人们已经倾注了大量的笔墨。然而,似乎没有人关心区分以下两个对象:使用JavaScript对象的共享*测试深度相等性*,javascript,ecmascript-6,Javascript,Ecmascript 6,在测试两个对象在JavaScript中的深度相等性这一主题上,人们已经倾注了大量的笔墨。然而,似乎没有人关心区分以下两个对象: var o1 = [{},{}]; var subitem = {}; var o2 = [subitem, subitem]; var o3 = [{}, {}]; 大多数深度相等算法会说o1,o2和o3是相等的。我想要一个算法,它表明o1和o2不相等,但是o1和o3相等。换言之,我需要一个算法来告诉我指针图是否具有相同的结构。我关心这一点,因为如果我有一个修改,第
var o1 = [{},{}];
var subitem = {};
var o2 = [subitem, subitem];
var o3 = [{}, {}];
大多数深度相等算法会说o1
,o2
和o3
是相等的。我想要一个算法,它表明o1
和o2
不相等,但是o1
和o3
相等。换言之,我需要一个算法来告诉我指针图是否具有相同的结构。我关心这一点,因为如果我有一个修改,第一个元素的修改会反映在o2
中的第二个元素中,但不会反映在o1
中
这意味着,循环结构的深度相等应起作用:
var o1 = [];
o1.push(o1);
var o2 = [];
o2.push(o2);
// deepGraphEqual(o1, o2) == true
var o3 = [[]];
o3[0].push(o3);
// deepGraphEqual(o1, o3) == false
如果要避免对项目进行变异,您可能需要ECMAScript 6映射,因此我将接受使用这些映射的解决方案。没有在二次时间内运行的ES6功能的版本:
function deepGraphEqual(a, b) {
var left = [], right = [], has = Object.prototype.hasOwnProperty;
function visit(a, b) {
var i, k;
if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null)
return a === b;
if (Object.getPrototypeOf(a) !== Object.getPrototypeOf(b))
return false;
for (i = 0; i < left.length; i++) {
if (a === left[i])
return b === right[i];
if (b === right[i])
return a === left[i];
}
for (k in a)
if (has.call(a, k) && !has.call(b, k))
return false;
for (k in b)
if (has.call(b, k) && !has.call(a, k))
return false;
left.push(a);
right.push(b);
for (k in a)
if (has.call(a, k) && !visit(a[k], b[k]))
return false;
return true;
}
return visit(a, b);
}
如何改进Anders Kaseorg的答案: 如果在超大数据结构上使用该算法,可能会出现堆栈溢出错误。例如,对于一个包含5000个节点的完整图,就会发生这种情况。所以我写了一个非递归版本,它使用广度优先搜索而不是深度优先搜索,因为这似乎更容易实现(当不使用递归时)。迭代版本适用于5000个节点的完整图形(不过在我的机器上需要6秒钟)。这是:
function deepEqual(item1, item2){
var EQUAL_ATOM = 1, UNEQUAL = 2, OBJECT = 3;
function compareSimple(first, second){
var ty1 = typeof first, ty2 = typeof second;
if (ty1!==ty2) return UNEQUAL;
if (ty1!=='object'){
if (first===second) return EQUAL_ATOM;
if ((ty1==='number')&&isNaN(first)&&isNaN(second)) return EQUAL_ATOM;
return UNEQUAL;
}
if (first===null) return (second===null) ? EQUAL_ATOM : UNEQUAL;
if (second===null) return UNEQUAL;
if (Object.getPrototypeOf(first) !== Object.getPrototypeOf(second)) return UNEQUAL;
return OBJECT;
}
var c = compareSimple(item1, item2);
if (c !== OBJECT) { return (c===EQUAL_ATOM); }
var stack1 = [], stack2 = [], inverse1 = new Map(), inverse2 = new Map();
stack1.push(item1); stack2.push(item2);
inverse1.set(item1, 0); inverse2.set(item2, 0);
var currentIdx = 0;
var firstItem, secondItem, i, own, has1, has2, key, kid1, kid2, itemCount;
while (currentIdx < stack1.length){
firstItem = stack1[currentIdx]; secondItem = stack2[currentIdx];
own = {};
for (key in firstItem){
has1 = firstItem.hasOwnProperty(key);
has2 = secondItem.hasOwnProperty(key);
if (has1 !== has2) return false;
if (has1) { own[key] = null; }
}
for (key in secondItem){
if (!(key in own)){
has1 = firstItem.hasOwnProperty(key);
has2 = secondItem.hasOwnProperty(key);
if (has1 !== has2) return false;
if (has1) { own[key] = null; }
}
}
for (key in own){
kid1 = firstItem[key];
kid2 = secondItem[key];
c = compareSimple(kid1, kid2);
if (c === UNEQUAL) return false;
if (c === OBJECT){
has1 = inverse1.has(kid1);
has2 = inverse2.has(kid2);
if (has1 !== has2) return false;
if (has1){
if (inverse1.get(kid1) !== inverse2.get(kid2)) { return false; }
} else {
itemCount = stack1.length;
stack1.push(kid1); stack2.push(kid2);
inverse1.set(kid1, itemCount); inverse2.set(kid2, itemCount);
}
}
}
++currentIdx;
}
return true;
}
因此,例如,任何2个
Date
s彼此deepgraphqual
,以及任何2个RegExp
s。那很可能不是你想要的。对于所有这些,我没有一个“包罗万象”的方法,而遍历所有现有的“内置”对象需要很长时间。但是对于日期和RegExp,你可以用.toString()
来比较它们。大多数深度相等算法都会说o1和o2是相等的-那么您想检查两个数组是否引用同一个对象,即,您想检查引用是否相等而不是值?只需使用==
o1==o2//false
正如你们所希望的,我澄清了问题,有一个误解。我不想测试o1和o2是否指向同一个对象,但我想测试它们的指针结构所代表的“图形”是否具有相同的结构是什么使得o2
不同于o3
?第二种算法看起来真的不是线性的。@djechlin:是的。对于图中的每个节点,我们所做的工作量与该节点的边数成比例。我们最多只处理一次每个节点,因此总工作量与图的大小(节点加边)成线性关系。如果(a.prototype!==b.prototype)应该做什么?那NaN
呢?这难道不应该被认为是平等的吗?在现有实现的基础上进行构建可能比使用自己的实现要好。@Bergi:我已将此更正为if(Object.getPrototypeOf(a)!==Object.getPrototypeOf(b))
@FelixKling:我考虑过这一点,并且我确实查看了现有的实现,例如node.jsassert.deepEqual()
,它也将NaN
视为不等同于自身。如果希望NaN
与自身相等,可以将a===b
测试替换为对象。is(a,b)
。
function deepEqual(item1, item2){
var EQUAL_ATOM = 1, UNEQUAL = 2, OBJECT = 3;
function compareSimple(first, second){
var ty1 = typeof first, ty2 = typeof second;
if (ty1!==ty2) return UNEQUAL;
if (ty1!=='object'){
if (first===second) return EQUAL_ATOM;
if ((ty1==='number')&&isNaN(first)&&isNaN(second)) return EQUAL_ATOM;
return UNEQUAL;
}
if (first===null) return (second===null) ? EQUAL_ATOM : UNEQUAL;
if (second===null) return UNEQUAL;
if (Object.getPrototypeOf(first) !== Object.getPrototypeOf(second)) return UNEQUAL;
return OBJECT;
}
var c = compareSimple(item1, item2);
if (c !== OBJECT) { return (c===EQUAL_ATOM); }
var stack1 = [], stack2 = [], inverse1 = new Map(), inverse2 = new Map();
stack1.push(item1); stack2.push(item2);
inverse1.set(item1, 0); inverse2.set(item2, 0);
var currentIdx = 0;
var firstItem, secondItem, i, own, has1, has2, key, kid1, kid2, itemCount;
while (currentIdx < stack1.length){
firstItem = stack1[currentIdx]; secondItem = stack2[currentIdx];
own = {};
for (key in firstItem){
has1 = firstItem.hasOwnProperty(key);
has2 = secondItem.hasOwnProperty(key);
if (has1 !== has2) return false;
if (has1) { own[key] = null; }
}
for (key in secondItem){
if (!(key in own)){
has1 = firstItem.hasOwnProperty(key);
has2 = secondItem.hasOwnProperty(key);
if (has1 !== has2) return false;
if (has1) { own[key] = null; }
}
}
for (key in own){
kid1 = firstItem[key];
kid2 = secondItem[key];
c = compareSimple(kid1, kid2);
if (c === UNEQUAL) return false;
if (c === OBJECT){
has1 = inverse1.has(kid1);
has2 = inverse2.has(kid2);
if (has1 !== has2) return false;
if (has1){
if (inverse1.get(kid1) !== inverse2.get(kid2)) { return false; }
} else {
itemCount = stack1.length;
stack1.push(kid1); stack2.push(kid2);
inverse1.set(kid1, itemCount); inverse2.set(kid2, itemCount);
}
}
}
++currentIdx;
}
return true;
}
now = new Date();
keys = Object.keys(now); // result: []