confrontando gli insiemi ECMA6 per l'uguaglianza


103

Come si confrontano due set di javascript? Ho provato a usare ==e ===ma entrambi restituiscono false.

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

Questi due insiemi sono equivalenti, perché per definizione, gli insiemi non hanno ordine (almeno non di solito). Ho esaminato la documentazione per Set su MDN e non ho trovato nulla di utile. Qualcuno sa come farlo?


Due set sono due oggetti diversi. ===è per l'uguaglianza di valore, non per l'uguaglianza degli oggetti.
elclanrs

3
iterare e confrontare il valore di ciascun membro, se tutto uguale, il set è "stesso"
dandavis

1
@dandavis Con gli insiemi, i membri sono i valori.

2
I set e le mappe hanno un ordine, che è l'ordine di inserzione, per qualsiasi motivo: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
CodeManX

8
Peggio di tutto, anche new Set([1,2,3]) != new Set([1,2,3]). Ciò rende Javascript Set inutile per i set di set perché il superset conterrà sottoinsiemi duplicati. L'unica soluzione alternativa che viene in mente è la conversione di tutti i sottoinsiemi in array, l'ordinamento di ogni array e la codifica di ogni array come stringa (ad esempio JSON).
7vujy0f0hy

Risposte:


74

Prova questo:

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

Un approccio più funzionale sarebbe:

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 allfunzione funziona per tutti gli oggetti iterabili (ad esempio Sete Map).

Se Array.fromfosse più ampiamente supportato, avremmo potuto implementare la allfunzione come:

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

Spero che aiuti.


2
Penso che si dovrebbe cambiare il nome hasa isPartOfo isInoelem
Bergi

1
@DavidGiven Sì, i set in JavaScript sono iterati in ordine di inserzione: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Aadit M Shah

49
Ogni giorno sono più sicuro che JavaScript sia il linguaggio più schifoso mai creato. Ognuno deve inventare le proprie funzioni di base per far fronte ai suoi limiti, e questo è in ES6, e siamo nel 2017! Perché non potevano aggiungere funzioni così frequenti alla specifica dell'oggetto Set!
Ghasan

2
@ GhasanAl-Sakkaf, sono d'accordo, forse è che TC39 è composto da scienziati, ma non pragmatici ...
Marecky

1
@TobiasFeil Haskell è ancora meglio.
Aadit M Shah

60

Puoi anche provare:

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


Sicuramente una soluzione migliore in quanto si adatta a una condizione if
Hugodby

Adoro quanto sia idiomatica questa soluzione e quanto leggibile! Grazie @Max
daydreamer

29

fornisce lodash_.isEqual() , che fa confronti profondi. Questo è molto utile se non vuoi scrivere il tuo. A partire da lodash 4, _.isEqual()confronta correttamente i set.

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

7

L'altra risposta funzionerà bene; ecco un'altra alternativa.

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

Tuttavia, tieni presente che questo non fa un confronto profondo sull'uguaglianza. Così

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

restituirà false. Se i due insiemi precedenti devono essere considerati uguali, dobbiamo iterare attraverso entrambi gli insiemi facendo confronti di qualità approfonditi su ogni elemento. Stabiliamo l'esistenza di una deepEqualroutine. Allora la logica sarebbe

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

Che cosa fa: per ogni membro di s1, cerca un membro profondamente uguale di s2. Se trovato, cancellalo in modo che non possa essere riutilizzato. I due insiemi sono profondamente uguali se tutti gli elementi in s1 si trovano in s2 e s2 è esaurito. Non testato.

Potresti trovarlo utile: http://www.2ality.com/2015/01/es6-set-operations.html .


6

Nessuna di queste soluzioni riporta la funzionalità prevista a una struttura di dati come un insieme di insiemi. Nel suo stato attuale, il set Javascript è inutile per questo scopo perché il superset conterrà sottoinsiemi duplicati, che Javascript vede erroneamente come distinti. L'unica soluzione a cui riesco a pensare è convertire ogni sottoinsieme in Array , ordinarlo e quindi codificarlo come String (ad esempio JSON).

Soluzione

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

Utilizzo di 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>

Ultimate test: set di set

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>


1
Ottima soluzione! E se sai che hai appena ricevuto una serie di stringhe o numeri, diventa semplicemente[...set1].sort().toString() === [...set2].sort().toString()

3

Il motivo per cui il tuo approccio restituisce falso è perché stai confrontando due oggetti diversi (anche se hanno lo stesso contenuto), quindi confrontare due oggetti diversi (non riferimenti, ma oggetti) ti restituisce sempre falso.

Il seguente approccio unisce due set in uno e confronta stupidamente le dimensioni. Se è lo stesso, è lo stesso:

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

Lato positivo : è molto semplice e breve. Nessuna libreria esterna solo vanilla JS

Svantaggio : probabilmente sarà più lento dell'iterazione sui valori e avrai bisogno di più spazio.


1

Confrontando due oggetti con ==, ===

Quando si utilizza l' operatore ==o ===per confrontare due oggetti, si otterrà sempre a false meno che tali oggetti non facciano riferimento allo stesso oggetto . Per esempio:

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

Altrimenti, == equivale a false anche se l'oggetto contiene gli stessi valori:

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

Potrebbe essere necessario considerare il confronto manuale

In ECMAScript 6, puoi convertire i set in array in anticipo in modo da poter individuare la differenza tra loro:

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

NOTA: Array.from è una delle funzionalità standard di ECMAScript 6 ma non è ampiamente supportata nei browser moderni. Controlla la tabella di compatibilità qui: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from#Browser_compatibility


1
Questo non mancherà di identificare i membri di bcui non fanno parte a?

1
@torazaburo Indeed. Il modo migliore per saltare il controllo se i membri di bnon sono presenti aè controllare se a.size === b.size.
Aadit M Shah il

1
Mettere a.size === b.sizeprima in cortocircuito il confronto dei singoli elementi se non necessario?

2
Se la dimensione è diversa, per definizione gli insiemi non sono uguali, quindi è meglio controllare prima quella condizione.

1
Bene, l'altro problema qui è che per la natura degli insiemi, l' hasoperazione sugli insiemi è progettata per essere molto efficiente, a differenza indexOfdell'operazione sugli array. Pertanto, avrebbe senso cambiare la funzione di filtro in return !b.has(i). Ciò eliminerebbe anche la necessità di convertire bin un array.

1

Ho creato un rapido polyfill per 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


1

Sulla base della risposta accettata, supponendo il supporto di Array.from, ecco una battuta:

function eqSet(a, b) {
    return a.size === b.size && Array.from(a).every(b.has.bind(b));
}

O un vero one-liner che assume le funzioni di freccia e l'operatore di diffusione: eqSet = (a,b) => a.size === b.size && [...a].every(b.has.bind(b))
John Hoffer

1

Se gli insiemi contengono solo tipi di dati primitivi o gli oggetti all'interno degli insiemi hanno l'uguaglianza dei riferimenti, allora c'è un modo più semplice

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


0

Seguo questo approccio nei test:

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

5
Aspetta un secondo ... questo controlla solo se A ha elementi che B non ha? Non controlla se B ha elementi che A non ha. Se provi a=[1,2,3]e b=[1,2,3,4], allora dice che sono la stessa cosa. Quindi immagino che tu abbia bisogno di un controllo extra comesetA.size === setB.size

0

Lieve modifica basata sulla risposta di @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
);

Se qualcun altro ha un problema come me a causa di qualche stranezza dell'ultima babele, ho dovuto aggiungere qui un condizionale esplicito.

(Anche al plurale penso aresia solo un po 'più intuitivo da leggere ad alta voce 🙃)


-1

1) Controlla se le dimensioni sono uguali. In caso contrario, non sono uguali.

2) itera su ogni elemento di A e controlla quello che esiste in B. Se uno fallisce, torna unequal

3) Se le 2 condizioni precedenti falliscono, significa che sono uguali.

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


Questa risposta è completamente sbagliata. L'istruzione return nel forEachmetodo NON restituirà la funzione genitore.
xaviert
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.