2016-11-15 13 views
3
Gleichheit von Kreisdatenstrukturen in JavaScript Bestimmung
var ones = [1]; 
ones[1] = ones; 
ones; 
// => [1, [1, [1, [1, [1, ...]]]]] 

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

Wie bestimmt man, dass ones und ones_ gleich sind? Gibt es einen Algorithmus, der zirkuläre Strukturen wie die obigen behandelt?Algorithmus zum

+1

Zur Klarstellung, möchten Sie auch das Objekt 'var foo = [1] betrachten; foo [1] = [1, foo]; 'den obigen Objekten gleich sein? –

+0

Korrigieren. Die Kennung, die zufällig einem Wert zugeordnet ist, ist nicht relevant. – davidchambers

+0

Speziell interessiert mich das Aktualisieren von ['Z.equals'] (https://github.com/sanctuary-js/sanctuary-type-classes#equals) ([Quelle] (https://github.com/sanctuary -js/scripto-type-classes/blob/v1.1.0/index.js # L912-L957)) äquivalente zirkuläre Strukturen als gleich behandeln. Der aktuelle Algorithmus erkennt Zirkelverweise, um eine endliche Rekursion zu vermeiden, gibt aber einfach 'false' zurück, sobald eine Zirkelreferenz erkannt wird. Ich hoffe, dass jemand mich auf eine Arbeit hinweisen kann, die einen allgemeinen Algorithmus beschreibt, der in einem JavaScript-Kontext anwendbar ist. – davidchambers

Antwort

3

Eine grundlegende Möglichkeit, dieses Problem zu lösen, ist zu beachten, dass während eines rekursiven Vergleich, wenn wir immer wieder am Ende wir vergleichen dasselbe Objektpaar, das wir bereits vergleichen, dann , wir können einfach annehmen, dass sie gleich sind. Das funktioniert, weil, wenn sie nicht gleich sind, dann der Vergleich, der bereits im Gange ist, schließlich einen Unterschied zwischen ihnen finden wird.

So können wir einfach mit einer grundlegenden rekursiven Vergleichsfunktion, starten und einen Stapel von Objekten hinzufügen zur Zeit verglichen werden:

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]]]));

Beachten Sie, dass ein großer Teil der Komplexität im Code Dies liegt daran, dass es versucht, eine Mischung aus verschiedenen Arten von JavaScript-Werten zu akzeptieren und (mehr oder weniger) elegant zu handhaben, einschließlich Primitiven, Nullwerten, Arrays, einfachen Objekten und all den anderen Dingen, die eine JS-Variable enthalten kann. Wenn Sie wissen, dass Ihre Eingaben nur eine begrenzte Anzahl von Datentypen enthalten können, ist es möglich, diesen Code erheblich zu vereinfachen.

Ps. Aufgrund des Stapelvergleichs kann die Laufzeit dieses Codes bis zu O (nd) sein, wobei n die Anzahl der Knoten in den Bäumen ist, die verglichen werden müssen (was mehr als erwartet sein kann; z. B. der Vergleich der Objekte seven und eleven im obigen Ausschnitt dauert 77 rekursive Aufrufe) und d ist die Tiefe des Stapels (die in diesem Fall auch bis zu 77). In ES2015 könnte eine potentiell nützliche Optimierung darin bestehen, ein Map von zuvor gesehenen Objekten zu verwenden, um den Stack-Lookup von O (d) auf effektiv O (1) zu reduzieren. Wenn wir erwarten, dass die zu vergleichenden Datenstrukturen im Allgemeinen viele sich wiederholende Verzweigungen aufweisen, die Verweise auf dieselben Objekte enthalten, könnte es sogar nützlich sein, diese in einen allgemeinen Cache von zuvor gesehenen Objekten zu expandieren, die wir bereits als gleichwertig erkannt haben.

2

Sie können ein Objekt durch Iterieren und Ersetzen bereits gesehener Objekte mit "Zeigern" (= Indizes in einem Array) "dezysten". Sobald Sie decycled structs bekommen, serialisiert sie einfach und vergleichen Sie als Strings:

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))

+1

Eine clevere Idee. Leider wird dieser Code meine 'var foo = [1] nicht korrekt identifizieren; foo [1] = [1, foo] 'Objekt als identisch mit den OPs 'var ones = [1]; Einsen [1] = Einsen. (Ein weiteres kleineres Problem ist, dass es ein falsches positives Ergebnis liefern könnte, wenn die Datenstruktur ein Objekt mit dem einzelnen Schlüssel "" -> "' und einem ganzzahligen Wert enthält.) –

+0

Die Tatsache, dass 'encode ([1, {'- > ': 0}]) 'und' encode (Einsen) 'sind äquivalent trotz' [1, {' -> ': 0}] 'nicht äquivalent zu' Einsen' ist ein Problem. Ich suche nach einem allgemeinen Algorithmus, der für jedes Paar von Arrays anwendbar ist. – davidchambers

+0

@IlmariKaronen: eine interessante Frage ist, wie formal "Gleichheit" solcher Strukturen, z. '1-> 2-> 3-> 1 ...' vs '1-> 2-> 3-> 1-> 2-> 3-> 1 ...'. Wie "zwei gefahrene Graphen werden als gleich angesehen, wenn ..." was? – georg

Verwandte Themen