comparaison d'ensembles ECMA6 pour l'égalité

103

Comment comparer deux ensembles javascript? J'ai essayé d'utiliser ==et ===mais les deux renvoient false.

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

Ces deux ensembles sont équivalents, car par définition, les ensembles n'ont pas d'ordre (du moins pas généralement). J'ai regardé la documentation de Set on MDN et je n'ai rien trouvé d'utile. Quelqu'un sait-il comment faire ça?

williamcodes
la source
Deux ensembles sont deux objets différents. ===est pour l'égalité des valeurs, pas l'égalité des objets.
elclanrs
3
itérer et comparer la valeur de chaque membre, si tout est identique, l'ensemble est "identique"
dandavis
1
@dandavis Avec les ensembles, les membres sont les valeurs.
2
Les ensembles et les cartes ont un ordre, qui est l'ordre d'insertion - pour une raison quelconque: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
CodeManX
8
Le pire de tout, même new Set([1,2,3]) != new Set([1,2,3]). Cela rend l' ensemble Javascript inutile pour les ensembles d'ensembles car le sur-ensemble contiendra des sous-ensembles en double. La seule solution de contournement qui me vient à l'esprit consiste à convertir tous les sous-ensembles en tableaux, à trier chaque tableau, puis à encoder chaque tableau sous forme de chaîne (par exemple JSON).
7vujy0f0hy

Réponses:

74

Essaye ça:

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;
}

Une approche plus fonctionnelle serait:

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

La allfonction fonctionne pour tous les objets itérables (par exemple Setet Map).

Si elle Array.frométait plus largement prise en charge, nous aurions pu implémenter la allfonction comme suit:

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

J'espère que cela pourra aider.

Aadit M Shah
la source
2
Je pense que vous devriez changer le nom de hasen isPartOfou isInouelem
Bergi
1
@DavidGiven Oui, les ensembles en JavaScript sont itérés dans l'ordre d'insertion: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Aadit M Shah
50
Chaque jour, je suis plus assuré que JavaScript est le langage le plus moche jamais créé. Chacun doit inventer ses propres fonctions de base pour faire face à sa limitation, et c'est dans ES6, et nous sommes en 2017! Pourquoi n'ont-ils pas pu ajouter des fonctions aussi fréquentes à la spécification de l'objet Set!
Ghasan
2
@ GhasanAl-Sakkaf, je suis d'accord, c'est peut-être que TC39 est composé de scientifiques, mais pas de pragmatiques ...
Marecky
1
@TobiasFeil Haskell est encore mieux.
Aadit M Shah
60

Vous pouvez également essayer:

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
la source
Certainement une meilleure solution car elle s'intègre dans une condition if
Hugodby
J'adore à quel point cette solution est idiomatique et à quel point elle est lisible! Merci @Max
Daydreamer
29

lodash fournit _.isEqual(), qui fait des comparaisons approfondies. Ceci est très pratique si vous ne voulez pas écrire le vôtre. À partir de lodash 4, _.isEqual()compare correctement les ensembles.

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
anthonysérieux
la source
7

L'autre réponse fonctionnera bien; voici une autre 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); }

Cependant, sachez que cela ne fait pas de comparaison d'égalité profonde. Alors

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

retournera faux. Si les deux ensembles ci-dessus doivent être considérés comme égaux, nous devons parcourir les deux ensembles en effectuant des comparaisons de qualité approfondies sur chaque élément. Nous stipulons l'existence d'une deepEqualroutine. Alors la logique serait

// 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;
}

Ce que cela fait: pour chaque membre de s1, recherchez un membre profondément égal de s2. S'il est trouvé, supprimez-le afin qu'il ne puisse plus être utilisé. Les deux ensembles sont profondément égaux si tous les éléments de s1 se trouvent dans s2 et si s2 est épuisé. Non testé.

Cela peut vous être utile: http://www.2ality.com/2015/01/es6-set-operations.html .


la source
6

Aucune de ces solutions ne «ramène» la fonctionnalité attendue à une structure de données telle qu'un ensemble d'ensembles. Dans son état actuel, l' ensemble Javascript est inutile à cette fin car le sur-ensemble contiendra des sous-ensembles dupliqués, que Javascript considère à tort comme distincts. La seule solution à laquelle je peux penser est de convertir chaque sous-ensemble en Array , de le trier puis de l'encoder en String (par exemple JSON).

Solution

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

Utilisation de base

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>

Test ultime: ensemble d'ensembles

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
la source
1
Excellente solution! Et si vous savez que vous venez d'avoir un ensemble de chaînes ou de nombres, cela devient simplement[...set1].sort().toString() === [...set2].sort().toString()
3

La raison pour laquelle votre approche retourne false est que vous comparez deux objets différents (même s'ils ont le même contenu), comparant ainsi deux objets différents (pas des références, mais des objets) vous renvoie toujours faux.

L'approche suivante fusionne deux ensembles en un seul et compare stupidement la taille. Si c'est pareil, c'est pareil:

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

À l'envers : C'est très simple et court. Pas de bibliothèque externe uniquement vanilla JS

Inconvénient : cela va probablement être plus lent qu'une simple itération sur les valeurs et vous avez besoin de plus d'espace.

thadeuszlay
la source
1

Comparaison de deux objets avec ==, ===

Lorsque vous utilisez l' opérateur ==ou ===pour comparer deux objets, vous obtiendrez toujours à false moins que ces objets ne font référence au même objet . Par exemple:

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

Sinon, == équivaut à false même si l'objet contient les mêmes valeurs:

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

Vous devrez peut-être envisager une comparaison manuelle

Dans ECMAScript 6, vous pouvez convertir au préalable des ensembles en tableaux afin de pouvoir repérer la différence entre eux:

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;
}

REMARQUE: Array.from est l'une des fonctionnalités standard d'ECMAScript 6, mais elle n'est pas largement prise en charge dans les navigateurs modernes. Vérifiez le tableau de compatibilité ici: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from#Browser_compatibility

TaoPR
la source
1
Cela ne manquera-t-il pas d'identifier le b dont ne sont pas membres a?
1
@torazaburo En effet. La meilleure façon de ne pas vérifier si les membres de bsont absents aest de vérifier si a.size === b.size.
Aadit M Shah
1
Mettre d' a.size === b.sizeabord à court-circuiter la comparaison des éléments individuels si ce n'est pas nécessaire?
2
Si la taille est différente, par définition, les ensembles ne sont pas égaux, il est donc préférable de vérifier d'abord cette condition.
1
Eh bien, l'autre problème ici est que par la nature des ensembles, l' hasopération sur les ensembles est conçue pour être très efficace, contrairement à l' indexOfopération sur les tableaux. Par conséquent, il serait logique de changer votre fonction de filtre pour qu'elle soit return !b.has(i). Cela éliminerait également le besoin de convertir ben un tableau.
1

J'ai créé un polyfill rapide pour Set.prototype.isEqual ()

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
la source
1

Sur la base de la réponse acceptée, en supposant le soutien de Array.from, voici une ligne unique:

function eqSet(a, b) {
    return a.size === b.size && Array.from(a).every(b.has.bind(b));
}
John Hoffer
la source
Ou un véritable one-liner assumant les fonctions fléchées et l'opérateur de propagation: eqSet = (a,b) => a.size === b.size && [...a].every(b.has.bind(b))
John Hoffer
1

Si les ensembles ne contiennent que des types de données primitifs ou si les objets à l'intérieur des ensembles ont une égalité de référence, il existe un moyen plus simple

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

Mikhail Unenov
la source
0

Je suis cette approche dans les 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
la source
5
Attendez une seconde ... cela vérifie seulement si A a des éléments que B n'a pas? Il ne vérifie pas si B a des éléments que A n'a pas. Si vous essayez a=[1,2,3]et b=[1,2,3,4], alors cela dit qu'ils sont les mêmes. Donc, je suppose que vous avez besoin d'une vérification supplémentaire commesetA.size === setB.size
0

Très légère modification basée sur la réponse de @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
);

Si quelqu'un d'autre a un problème comme moi en raison d'une bizarrerie de la dernière babel, il fallait ajouter un conditionnel explicite ici.

(Aussi pour le pluriel, je pense que arec'est juste un peu plus intuitif à lire à haute voix 🙃)

rob2d
la source
-1

1) Vérifiez si les tailles sont égales. Sinon, ils ne sont pas égaux.

2) itérer sur chaque élément de A et archiver qui existe dans B.Si l'un échoue, retournez unequal

3) Si les 2 conditions ci-dessus échouent, cela signifie qu'elles sont égales.

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) Méthode 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
la source
Cette réponse est complètement incorrecte. L'instruction return dans la forEachméthode ne rendra PAS la fonction parent de retour.
xaviert