用于确定JavaScript中循环数据结构的相等性的算法

时间:2016-11-15 23:58:36

标签: javascript functional-programming equality

var ones = [1];
ones[1] = ones;
ones;
// => [1, [1, [1, [1, [1, ...]]]]]

var ones_ = [1];
ones_[1] = ones_;
ones_;
// => [1, [1, [1, [1, [1, ...]]]]]

如何确定onesones_相等?是否存在处理循环结构的算法,如上所述?

2 个答案:

答案 0 :(得分:4)

解决这个问题的一个基本方法是注意,如果在递归比较期间,我们最终再次比较我们已经比较的同一对对象,那么我们可能只是假设他们是平等的。这是有效的,因为如果他们毕竟 不相等,那么正在进行的比较最终会发现它们之间存在一些差异。< / p>

因此,我们可以简单地从一个基本的递归比较函数开始,并添加一组当前正在比较的对象:

&#13;
&#13;
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]]]));
&#13;
&#13;
&#13;

请注意,上面代码中的很多复杂性是由于它试图接受并且(或多或少)优雅地处理不同类型的JavaScript值的任何混合,包括基元,空值,数组,普通对象和所有JS变量可以包含的其他杂项。如果您知道您的输入只能包含有限范围的数据类型,则可以大大简化此代码。

聚苯乙烯。由于堆栈比较,此代码的运行时最长可达O( nd ),其中 n 是树中需要比较的节点数(这可能超出预期;例如,比较上面代码段中的对象seveneleven需要77个递归调用)而 d 是堆栈的深度(在这种情况下,也达到了77)。在ES2015中,可能有用的优化可能是使用先前看到的Map个对象来减少从O( d )到有效O(1)的堆栈查找。如果我们期望被比较的数据结构通常具有许多包含对相同对象的引用的重复分支,那么将它扩展为我们已经发现的相同的先前对象的通用缓存甚至可能是有用的。

答案 1 :(得分:2)

你可以通过迭代它并用“指针”(=某些数组中的索引)替换已经看过的对象来“解除”对象的“循环”。一旦你获得了decycled结构,只需序列化它们并比较为字符串:

Artist