Vergleichen von ECMA6-Sets auf Gleichheit

102

Wie vergleicht man zwei Javascript-Sets? Ich habe versucht, ==und ===beide geben false zurück.

a = new Set([1,2,3]);
b = new Set([1,3,2]);
a == b; //=> false
a === b; //=> false

Diese beiden Mengen sind äquivalent, da Mengen per Definition keine Reihenfolge haben (zumindest normalerweise nicht). Ich habe mir die Dokumentation zu Set on MDN angesehen und nichts Nützliches gefunden. Weiß jemand, wie man das macht?

Williamcodes
quelle
Zwei Sätze sind zwei verschiedene Objekte. ===steht für Wertgleichheit, nicht für Objektgleichheit.
Elclanrs
3
iterieren und vergleichen Sie den Wert jedes Mitglieds, wenn alle gleich sind, ist der Satz "gleich"
dandavis
1
@dandavis Mit Sets, die Mitglieder sind die Werte.
2
Sets und Maps haben eine Reihenfolge, die die
Einfügereihenfolge
7
Am schlimmsten sogar new Set([1,2,3]) != new Set([1,2,3]). Dies macht Javascript Set für Sätze von Sätzen unbrauchbar, da die Obermenge doppelte Teilmengen enthält. Die einzige Problemumgehung, die Ihnen in den Sinn kommt, besteht darin, alle Teilmengen in Arrays zu konvertieren, jedes Array zu sortieren und dann jedes Array als Zeichenfolge zu codieren (z. B. JSON).
7vujy0f0hy

Antworten:

73

Versuche dies:

var a = new Set([1,2,3]);
var b = new Set([1,3,2]);

alert(eqSet(a, b)); // true

function eqSet(as, bs) {
    if (as.size !== bs.size) return false;
    for (var a of as) if (!bs.has(a)) return false;
    return true;
}

Ein funktionalerer Ansatz wäre:

var a = new Set([1,2,3]);
var b = new Set([1,3,2]);

alert(eqSet(a, b)); // true

function eqSet(as, bs) {
    return as.size === bs.size && all(isIn(bs), as);
}

function all(pred, as) {
    for (var a of as) if (!pred(a)) return false;
    return true;
}

function isIn(as) {
    return function (a) {
        return as.has(a);
    };
}

Die allFunktion funktioniert für alle iterierbaren Objekte (zB Setund Map).

Wenn dies Array.fromweiter unterstützt würde, hätten wir die allFunktion wie folgt implementieren können:

function all(pred, as) {
    return Array.from(as).every(pred);
}

Hoffentlich hilft das.

Aadit M Shah
quelle
2
Ich denke , Sie sollten die Namen ändern haszu isPartOfoder isInoderelem
Bergi
@Bergi Ich habe den Namen der Funktion hasin geändert isIn.
Aadit M Shah
1
@DavidGiven Ja, Sätze in JavaScript werden in der
Einfügereihenfolge
47
Jeden Tag bin ich mir sicherer, dass JavaScript die mieseste Sprache ist, die jemals erstellt wurde. Jeder muss seine eigenen Grundfunktionen erfinden, um mit seiner Einschränkung fertig zu werden. Dies ist in ES6 und wir sind in 2017! Warum konnten sie der Spezifikation des Set-Objekts nicht so häufige Funktionen hinzufügen?
Ghasan
2
@ GhasanAl-Sakkaf, ich stimme zu, es ist vielleicht, dass TC39 aus Wissenschaftlern besteht, aber keine Pragmatiker ...
Marecky
59

Sie können auch versuchen:

var a = new Set([1,2,3]);
var b = new Set([1,3,2]);

isSetsEqual = (a, b) => a.size === b.size && [...a].every(value => b.has(value));

console.log(isSetsEqual(a,b)) 

Max Leizerovich
quelle
Auf jeden Fall eine bessere Lösung, da sie in eine if-Bedingung passt
Hugodby
Ich finde es toll, wie idiomatisch diese Lösung ist und wie viel lesbar! Danke @Max
Tagträumer
29

lodash bietet _.isEqual(), was tiefe Vergleiche macht. Dies ist sehr praktisch, wenn Sie keine eigenen schreiben möchten. Ab Lodash 4 _.isEqual()werden die Sets ordnungsgemäß verglichen.

const _ = require("lodash");

let s1 = new Set([1,2,3]);
let s2 = new Set([1,2,3]);
let s3 = new Set([2,3,4]);

console.log(_.isEqual(s1, s2)); // true
console.log(_.isEqual(s1, s3)); // false
anthonyserious
quelle
7

Die andere Antwort wird gut funktionieren; Hier ist eine andere Alternative.

// Create function to check if an element is in a specified set.
function isIn(s)          { return elt => s.has(elt); }

// Check if one set contains another (all members of s2 are in s1).
function contains(s1, s2) { return [...s2] . every(isIn(s1)); }

// Set equality: a contains b, and b contains a
function eqSet(a, b)      { return contains(a, b) && contains(b, a); }

// Alternative, check size first
function eqSet(a, b)      { return a.size === b.size && contains(a, b); }

Beachten Sie jedoch, dass dies nicht der Fall ist tiefe Gleichheit Vergleich zu tun. So

eqSet(Set([{ a: 1 }], Set([{ a: 1 }])

wird false zurückgeben. Wenn die beiden oben genannten Sätze als gleich angesehen werden sollen, müssen wir beide Sätze durchlaufen und tiefe Qualitätsvergleiche für jedes Element durchführen. Wir schreiben die Existenz einer deepEqualRoutine vor. Dann wäre die Logik

// Find a member in "s" deeply equal to some value
function findDeepEqual(s, v) { return [...s] . find(m => deepEqual(v, m)); }

// See if sets s1 and s1 are deeply equal. DESTROYS s2.
function eqSetDeep(s1, s2) {
  return [...s1] . every(a1 => {
    var m1 = findDeepEqual(s2, a1);
    if (m1) { s2.delete(m1); return true; }
  }) && !s2.size;
}

Was dies bedeutet: Suchen Sie für jedes Mitglied von s1 nach einem zutiefst gleichberechtigten Mitglied von s2. Wenn gefunden, löschen Sie es, damit es nicht wieder verwendet werden kann. Die beiden Mengen sind zutiefst gleich, wenn alle Elemente in s1 in s2 gefunden werden. und s2 erschöpft ist. Ungetestet.

Dies kann hilfreich sein: http://www.2ality.com/2015/01/es6-set-operations.html .


quelle
6

Keine dieser Lösungen bringt die erwartete Funktionalität in eine Datenstruktur wie einen Satz von Sätzen zurück. In seinem aktuellen Zustand ist die Javascript- Menge für diesen Zweck unbrauchbar, da die Obermenge doppelte Teilmengen enthält, die Javascript fälschlicherweise als verschieden ansieht. Die einzige Lösung, die ich mir vorstellen kann, besteht darin, jede Teilmenge in ein Array zu konvertieren , zu sortieren und dann als String zu codieren (zum Beispiel JSON).

Lösung

var toJsonSet = aset /* array or set */ => JSON.stringify([...new Set(aset)].sort()); 
var fromJsonSet = jset => new Set(JSON.parse(jset));

Grundlegende Verwendung

var toJsonSet = aset /* array or set */ => JSON.stringify([...new Set(aset)].sort()); 
var fromJsonSet = jset => new Set(JSON.parse(jset));

var [s1,s2] = [new Set([1,2,3]), new Set([3,2,1])];
var [js1,js2] = [toJsonSet([1,2,3]), toJsonSet([3,2,1])]; // even better

var r = document.querySelectorAll("td:nth-child(2)");
r[0].innerHTML = (toJsonSet(s1) === toJsonSet(s2)); // true
r[1].innerHTML = (toJsonSet(s1) == toJsonSet(s2)); // true, too
r[2].innerHTML = (js1 === js2); // true
r[3].innerHTML = (js1 == js2); // true, too

// Make it normal Set:
console.log(fromJsonSet(js1), fromJsonSet(js2)); // type is Set
<style>td:nth-child(2) {color: red;}</style>

<table>
<tr><td>toJsonSet(s1) === toJsonSet(s2)</td><td>...</td></tr>
<tr><td>toJsonSet(s1) == toJsonSet(s2)</td><td>...</td></tr>
<tr><td>js1 === js2</td><td>...</td></tr>
<tr><td>js1 == js2</td><td>...</td></tr>
</table>

Ultimativer Test: Satz von Sätzen

var toSet = arr => new Set(arr);
var toJsonSet = aset /* array or set */ => JSON.stringify([...new Set(aset)].sort()); 
var toJsonSet_WRONG = set => JSON.stringify([...set]); // no sorting!

var output = document.getElementsByTagName("code"); 
var superarray = [[1,2,3],[1,2,3],[3,2,1],[3,6,2],[4,5,6]];
var superset;

Experiment1:
    superset = toSet(superarray.map(toSet));
    output[0].innerHTML = superset.size; // incorrect: 5 unique subsets
Experiment2:
    superset = toSet([...superset].map(toJsonSet_WRONG));
    output[1].innerHTML = superset.size; // incorrect: 4 unique subsets
Experiment3:
    superset = toSet([...superset].map(toJsonSet));
    output[2].innerHTML = superset.size; // 3 unique subsets
Experiment4:
    superset = toSet(superarray.map(toJsonSet));
    output[3].innerHTML = superset.size; // 3 unique subsets
code {border: 1px solid #88f; background-color: #ddf; padding: 0 0.5em;}
<h3>Experiment 1</h3><p>Superset contains 3 unique subsets but Javascript sees <code>...</code>.<br>Let’s fix this... I’ll encode each subset as a string.</p>
<h3>Experiment 2</h3><p>Now Javascript sees <code>...</code> unique subsets.<br>Better! But still not perfect.<br>That’s because we didn’t sort each subset.<br>Let’s sort it out...</p>
<h3>Experiment 3</h3><p>Now Javascript sees <code>...</code> unique subsets. At long last!<br>Let’s try everything again from the beginning.</p>
<h3>Experiment 4</h3><p>Superset contains 3 unique subsets and Javascript sees <code>...</code>.<br><b>Bravo!</b></p>

7vujy0f0hy
quelle
1
Tolle Lösung! Und wenn Sie wissen, dass Sie nur eine Reihe von Zeichenfolgen oder Zahlen haben, wird es einfach[...set1].sort().toString() === [...set2].sort().toString()
3

Der Grund, warum Ihr Ansatz false zurückgibt, liegt darin, dass Sie zwei verschiedene Objekte vergleichen (auch wenn sie denselben Inhalt haben). Wenn Sie also zwei verschiedene Objekte (keine Referenzen, sondern Objekte) vergleichen, werden Sie immer falsch zurückgegeben.

Der folgende Ansatz führt zwei Sätze zu einem zusammen und vergleicht die Größe nur dumm. Wenn es dasselbe ist, ist es dasselbe:

const a1 = [1,2,3];
const a2 = [1,3,2];
const set1 = new Set(a1);
const set2 = new Set(a2);

const compareSet = new Set([...a1, ...a2]);
const isSetEqual = compareSet.size === set2.size && compareSet.size === set1.size;
console.log(isSetEqual);

Kopf : Es ist sehr einfach und kurz. Keine externe Bibliothek nur Vanilla JS

Nachteil : Es wird wahrscheinlich langsamer sein, als nur die Werte zu durchlaufen, und Sie benötigen mehr Platz.

Thadeuszlay
quelle
1

Vergleich zweier Objekte mit ==, ===

Wenn Sie mit ==oder ===operator zwei Objekte vergleichen, erhalten Sie immer, es false sei denn, diese Objekte verweisen auf dasselbe Objekt . Beispielsweise:

var a = b = new Set([1,2,3]); // NOTE: b will become a global variable
a == b; // <-- true: a and b share the same object reference

Andernfalls entspricht == false, obwohl das Objekt dieselben Werte enthält:

var a = new Set([1,2,3]);
var b = new Set([1,2,3]);
a == b; // <-- false: a and b are not referencing the same object

Möglicherweise müssen Sie einen manuellen Vergleich in Betracht ziehen

In ECMAScript 6 können Sie Sets vorher in Arrays konvertieren, um den Unterschied zwischen ihnen zu erkennen:

function setsEqual(a,b){
    if (a.size !== b.size)
        return false;
    let aa = Array.from(a); 
    let bb = Array.from(b);
    return aa.filter(function(i){return bb.indexOf(i)<0}).length==0;
}

HINWEIS: Array.from ist eine der Standardfunktionen von ECMAScript 6, wird jedoch in modernen Browsern nicht allgemein unterstützt. Überprüfen Sie die Kompatibilitätstabelle hier: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from#Browser_compatibility

TaoPR
quelle
1
Wird dies nicht dazu führen, dass Mitglieder identifiziert bwerden, die nicht dabei sind a?
1
@ Torazaburo In der Tat. Der beste Weg, um zu überspringen, ob Mitglieder von bnicht anwesend sind, abesteht darin, zu prüfen, ob a.size === b.size.
Aadit M Shah
1
Stellen Sie a.size === b.sizezunächst den Vergleich einzelner Elemente kurz, wenn dies nicht erforderlich ist?
2
Wenn die Größe unterschiedlich ist, sind die Mengen per Definition nicht gleich. Überprüfen Sie diese Bedingung daher zuerst.
1
Das andere Problem hierbei ist, dass die hasOperation an Sets aufgrund der Art der Mengen im Gegensatz zur indexOfOperation an Arrays sehr effizient ausgelegt ist . Daher wäre es sinnvoll, die Filterfunktion zu ändern return !b.has(i). Dies würde auch die Notwendigkeit einer Konvertierung bin ein Array beseitigen .
1

Ich habe eine schnelle Polyfüllung für Set.prototype.isEqual () erstellt.

Set.prototype.isEqual = function(otherSet) {
    if(this.size !== otherSet.size) return false;
    for(let item of this) if(!otherSet.has(item)) return false;
    return true;
}

Github Gist - Set.prototype.isEqual

Lenny
quelle
1

Basierend auf der akzeptierten Antwort, unter der Annahme der Unterstützung von Array.from, ist hier ein Einzeiler:

function eqSet(a, b) {
    return a.size === b.size && Array.from(a).every(b.has.bind(b));
}
John Hoffer
quelle
Oder ein echter Einzeiler, der Pfeilfunktionen und den Spread-Operator übernimmt: eqSet = (a,b) => a.size === b.size && [...a].every(b.has.bind(b))
John Hoffer
1

Wenn Mengen nur primitive Datentypen enthalten oder Objekte in Mengen Referenzgleichheit haben, gibt es einen einfacheren Weg

const isEqualSets = (set1, set2) => (set1.size === set2.size) && (set1.size === new Set([...set1, ...set2]).size);

Mikhail Unenov
quelle
0

Ich verfolge diesen Ansatz in Tests:

let setA = new Set(arrayA);
let setB = new Set(arrayB);
let diff = new Set([...setA].filter(x => !setB.has(x)));
expect([...diff].length).toBe(0);
Francisco
quelle
5
Warten Sie eine Sekunde ... dies prüft nur, ob A Elemente hat, die B nicht hat? Es wird nicht geprüft, ob B Elemente enthält, die A nicht hat. Wenn Sie versuchen a=[1,2,3]und b=[1,2,3,4], dann heißt es, dass sie gleich sind. Also ich denke du brauchst einen zusätzlichen Scheck wiesetA.size === setB.size
0

Sehr geringfügige Änderung basierend auf der Antwort von @Aadit M Shah:

/**
 * check if two sets are equal in the sense that
 * they have a matching set of values.
 *
 * @param {Set} a 
 * @param {Set} b
 * @returns {Boolean} 
 */
const areSetsEqual = (a, b) => (
        (a.size === b.size) ? 
        [...a].every( value => b.has(value) ) : false
);

Wenn jemand anderes ein Problem hat, wie ich es aufgrund einer Eigenart des neuesten Babel getan habe, musste hier eine explizite Bedingung hinzugefügt werden.

(Auch für den Plural finde ich arees etwas intuitiver vorzulesen 🙃)

rob2d
quelle
-1

1) Überprüfen Sie, ob die Größen gleich sind. Wenn nicht, sind sie nicht gleich.

2) Iterieren Sie über jedes Element von A und checken Sie das in B vorhandene ein. Wenn eines fehlschlägt, kehren Sie zurück unequal

3) Wenn die obigen 2 Bedingungen fehlschlagen, bedeutet dies, dass sie gleich sind.

let isEql = (setA, setB) => {
  if (setA.size !== setB.size)
    return false;
  
  setA.forEach((val) => {
    if (!setB.has(val))
      return false;
  });
  return true;
}

let setA = new Set([1, 2, {
  3: 4
}]);
let setB = new Set([2, {
    3: 4
  },
  1
]);

console.log(isEql(setA, setB));

2) Methode 2

let isEql = (A, B) => {
  return JSON.stringify([...A].sort()) == JSON.stringify([...B].sort());
}

let res = isEql(new Set([1, 2, {3:4}]), new Set([{3:4},1, 2]));
console.log(res);

sapy
quelle
Diese Antwort ist völlig falsch. Die return-Anweisung in der forEachMethode führt NICHT dazu, dass die übergeordnete Funktion zurückgegeben wird.
xaviert