确定 JavaScript 中循环数据结构是否相等的算法
Algorithm for determining equality of circular data structures in JavaScript
var ones = [1];
ones[1] = ones;
ones;
// => [1, [1, [1, [1, [1, ...]]]]]
var ones_ = [1];
ones_[1] = ones_;
ones_;
// => [1, [1, [1, [1, [1, ...]]]]]
如何判断ones
和ones_
相等?是否有处理上述圆形结构的算法?
您可以 'decycle' 一个对象,方法是迭代它并将已经看到的对象替换为 "pointers"(= 某些数组中的索引)。一旦你得到回收结构,只需将它们序列化并作为字符串进行比较:
let encode = function (x) {
function _enc(x, lst) {
if (typeof x !== 'object')
return x;
let i = lst.indexOf(x);
if (i >= 0)
return {'->': i};
lst.push(x);
let y = {};
for (let k of Object.keys(x))
y[k] = _enc(x[k], lst)
return y;
}
return JSON.stringify(_enc(x, []));
};
//////
let ones = [1];
ones[1] = ones;
let ones_ = [1];
ones_[1] = ones_;
console.log(encode(ones) === encode(ones_))
// more interesting example
a = {
b: {
c: 123
}
};
a.b.d = a;
a.x = [9, a.b];
a2 = {
b: {
c: 123
}
};
a2.b.d = a2;
a2.x = [9, a2.b];
console.log(encode(a) === encode(a2))
解决这个问题的一个基本方法是注意,如果在递归比较期间,我们最终再次比较我们已经比较过的同一对对象,那么 我们可以简单地假设它们是相等的。 这是有效的,因为如果它们毕竟 不 相等,那么已经在进行的比较最终会发现它们之间的一些差异.
因此,我们可以简单地从一个基本的递归比较函数开始,并添加当前正在比较的对象堆栈:
function isEqual (a, b) {
var stack = [];
function _isEqual (a, b) {
// console.log("->", stack.length);
// handle some simple cases first
if (a === b) return true;
if (typeof(a) !== "object" || typeof(b) !== "object") return false;
// XXX: typeof(null) === "object", but Object.getPrototypeOf(null) throws!
if (a === null || b === null) return false;
var proto = Object.getPrototypeOf(a);
if (proto !== Object.getPrototypeOf(b)) return false;
// assume that non-identical objects of unrecognized type are not equal
// XXX: could add code here to properly compare e.g. Date objects
if (proto !== Object.prototype && proto !== Array.prototype) return false;
// check the stack before doing a recursive comparison
for (var i = 0; i < stack.length; i++) {
if (a === stack[i][0] && b === stack[i][1]) return true;
// if (b === stack[i][0] && a === stack[i][1]) return true;
}
// do the objects even have the same keys?
for (var prop in a) if (!(prop in b)) return false;
for (var prop in b) if (!(prop in a)) return false;
// nothing to do but recurse!
stack.push([a, b]);
for (var prop in a) {
if (!(_isEqual(a[prop], b[prop]))) {
stack.pop();
return false;
}
}
stack.pop();
return true;
}
return _isEqual(a, b);
}
// TEST CASES:
var ones = [1]; ones[1] = ones;
var foo = [1]; foo[1] = [1, foo];
var bar = [1]; bar[1] = [1, ones];
console.log("ones == foo:", isEqual(ones, foo));
console.log("ones == bar:", isEqual(ones, bar));
console.log("foo == bar:", isEqual(foo, bar));
var obj = {}; obj["x"] = obj; obj["y"] = {obj};
console.log("obj == obj[x]:", isEqual(obj, obj["x"]));
console.log("obj != obj[y]:", !isEqual(obj, obj["y"]));
var seven = []; seven[0] = [[[[[[seven]]]]]];
var eleven = []; eleven[0] = [[[[[[[[[[eleven]]]]]]]]]];
console.log("seven == eleven:", isEqual(seven, eleven));
console.log("[seven] == [eleven]:", isEqual([seven], [eleven]));
console.log("[seven] == seven:", isEqual([seven], seven));
console.log("[seven] == [[[eleven]]]:", isEqual([seven], [[[eleven]]]));
请注意,上面代码中的很多复杂性是由于它试图接受并(或多或少)优雅地处理不同类型 JavaScript 值的任何混合,包括基元、空值、数组、普通对象和 JS 变量可以包含的所有其他杂项。如果您知道您的输入只能包含有限范围的数据类型,则可以大大简化此代码。
Ps。由于堆栈比较,此代码的运行时间可以达到 O(nd),其中 n 是树中的节点数需要比较的(可能比预期的要多;例如,比较上面代码段中的对象 seven
和 eleven
需要 77 次递归调用)和 d 是堆栈的深度(在这种情况下,也达到 77)。在 ES2015 中,一个可能有用的优化可能是使用 Map 个以前见过的对象来将堆栈查找从 O(d) 减少到有效的 O(1)。
var ones = [1];
ones[1] = ones;
ones;
// => [1, [1, [1, [1, [1, ...]]]]]
var ones_ = [1];
ones_[1] = ones_;
ones_;
// => [1, [1, [1, [1, [1, ...]]]]]
如何判断ones
和ones_
相等?是否有处理上述圆形结构的算法?
您可以 'decycle' 一个对象,方法是迭代它并将已经看到的对象替换为 "pointers"(= 某些数组中的索引)。一旦你得到回收结构,只需将它们序列化并作为字符串进行比较:
let encode = function (x) {
function _enc(x, lst) {
if (typeof x !== 'object')
return x;
let i = lst.indexOf(x);
if (i >= 0)
return {'->': i};
lst.push(x);
let y = {};
for (let k of Object.keys(x))
y[k] = _enc(x[k], lst)
return y;
}
return JSON.stringify(_enc(x, []));
};
//////
let ones = [1];
ones[1] = ones;
let ones_ = [1];
ones_[1] = ones_;
console.log(encode(ones) === encode(ones_))
// more interesting example
a = {
b: {
c: 123
}
};
a.b.d = a;
a.x = [9, a.b];
a2 = {
b: {
c: 123
}
};
a2.b.d = a2;
a2.x = [9, a2.b];
console.log(encode(a) === encode(a2))
解决这个问题的一个基本方法是注意,如果在递归比较期间,我们最终再次比较我们已经比较过的同一对对象,那么 我们可以简单地假设它们是相等的。 这是有效的,因为如果它们毕竟 不 相等,那么已经在进行的比较最终会发现它们之间的一些差异.
因此,我们可以简单地从一个基本的递归比较函数开始,并添加当前正在比较的对象堆栈:
function isEqual (a, b) {
var stack = [];
function _isEqual (a, b) {
// console.log("->", stack.length);
// handle some simple cases first
if (a === b) return true;
if (typeof(a) !== "object" || typeof(b) !== "object") return false;
// XXX: typeof(null) === "object", but Object.getPrototypeOf(null) throws!
if (a === null || b === null) return false;
var proto = Object.getPrototypeOf(a);
if (proto !== Object.getPrototypeOf(b)) return false;
// assume that non-identical objects of unrecognized type are not equal
// XXX: could add code here to properly compare e.g. Date objects
if (proto !== Object.prototype && proto !== Array.prototype) return false;
// check the stack before doing a recursive comparison
for (var i = 0; i < stack.length; i++) {
if (a === stack[i][0] && b === stack[i][1]) return true;
// if (b === stack[i][0] && a === stack[i][1]) return true;
}
// do the objects even have the same keys?
for (var prop in a) if (!(prop in b)) return false;
for (var prop in b) if (!(prop in a)) return false;
// nothing to do but recurse!
stack.push([a, b]);
for (var prop in a) {
if (!(_isEqual(a[prop], b[prop]))) {
stack.pop();
return false;
}
}
stack.pop();
return true;
}
return _isEqual(a, b);
}
// TEST CASES:
var ones = [1]; ones[1] = ones;
var foo = [1]; foo[1] = [1, foo];
var bar = [1]; bar[1] = [1, ones];
console.log("ones == foo:", isEqual(ones, foo));
console.log("ones == bar:", isEqual(ones, bar));
console.log("foo == bar:", isEqual(foo, bar));
var obj = {}; obj["x"] = obj; obj["y"] = {obj};
console.log("obj == obj[x]:", isEqual(obj, obj["x"]));
console.log("obj != obj[y]:", !isEqual(obj, obj["y"]));
var seven = []; seven[0] = [[[[[[seven]]]]]];
var eleven = []; eleven[0] = [[[[[[[[[[eleven]]]]]]]]]];
console.log("seven == eleven:", isEqual(seven, eleven));
console.log("[seven] == [eleven]:", isEqual([seven], [eleven]));
console.log("[seven] == seven:", isEqual([seven], seven));
console.log("[seven] == [[[eleven]]]:", isEqual([seven], [[[eleven]]]));
请注意,上面代码中的很多复杂性是由于它试图接受并(或多或少)优雅地处理不同类型 JavaScript 值的任何混合,包括基元、空值、数组、普通对象和 JS 变量可以包含的所有其他杂项。如果您知道您的输入只能包含有限范围的数据类型,则可以大大简化此代码。
Ps。由于堆栈比较,此代码的运行时间可以达到 O(nd),其中 n 是树中的节点数需要比较的(可能比预期的要多;例如,比较上面代码段中的对象 seven
和 eleven
需要 77 次递归调用)和 d 是堆栈的深度(在这种情况下,也达到 77)。在 ES2015 中,一个可能有用的优化可能是使用 Map 个以前见过的对象来将堆栈查找从 O(d) 减少到有效的 O(1)。