porównanie zestawów ECMA6 pod kątem równości

104

Jak porównać dwa zestawy javascript? Próbowałem za pomocą ==a ===jednak zarówno fałszywie zwrotny.

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

Te dwa zbiory są równoważne, ponieważ z definicji zbiory nie mają porządku (przynajmniej nie zwykle). Przejrzałem dokumentację Set on MDN i nie znalazłem nic przydatnego. Czy ktoś wie, jak to zrobić?

williamcodes
źródło
Dwa zestawy to dwa różne obiekty. ===dotyczy równości wartości, a nie równości obiektów.
elclanrs
3
iteruj i porównuj wartości każdego elementu członkowskiego, jeśli wszystkie są takie same, ustaw jako „ten sam”
dandavis
1
@dandavis W przypadku zestawów elementy członkowskie wartościami.
2
Zestawy i mapy mają kolejność, która jest kolejnością wstawiania - z dowolnego powodu: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/ ...
CodeManX
8
A co najgorsze new Set([1,2,3]) != new Set([1,2,3]). To sprawia, że zestaw JavaScript jest bezużyteczny dla zestawów zestawów, ponieważ nadzbiór będzie zawierał zduplikowane podzbiory. Jedynym obejściem, jakie przychodzi na myśl, jest konwersja wszystkich podzbiorów na tablice, sortowanie każdej tablicy, a następnie kodowanie każdej tablicy jako ciągu (na przykład JSON).
7vujy0f0hy

Odpowiedzi:

74

Spróbuj tego:

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

Bardziej funkcjonalnym podejściem byłoby:

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

allFunkcja działa dla wszystkich obiektów (np iterable Seti Map).

Gdyby Array.frombył szerzej obsługiwany, moglibyśmy zaimplementować allfunkcję jako:

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

Mam nadzieję, że to pomoże.

Aadit M Shah
źródło
2
Myślę, że powinieneś zmienić nazwę hasna isPartOflub isInlubelem
Bergi
1
@DavidGiven Tak, zestawy w JavaScript są iterowane w kolejności wstawiania: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Aadit M Shah
51
Każdego dnia jestem bardziej przekonany, że JavaScript jest najgorszym językiem, jaki kiedykolwiek powstał. Każdy musi wymyślić własne podstawowe funkcje, aby poradzić sobie z jego ograniczeniami, a to jest w ES6, a my mamy 2017! Dlaczego nie mogli dodać tak częstych funkcji do specyfikacji obiektu Set!
Ghasan,
2
@ GhasanAl-Sakkaf, zgadzam się, być może TC39 składa się z naukowców, ale nie ma pragmatyków ...
Marecky
1
@TobiasFeil Haskell jest jeszcze lepszy.
Aadit M Shah
61

Możesz też spróbować:

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
źródło
Zdecydowanie lepsze rozwiązanie, ponieważ pasuje do warunku if
Hugodby
Uwielbiam to, jak idiomatyczne jest to rozwiązanie i jak bardzo czytelne! Dzięki @Max
daydreamer
29

zapewnia lodash_.isEqual() , który dokonuje głębokich porównań. Jest to bardzo przydatne, jeśli nie chcesz pisać własnego. Od lodash 4 _.isEqual()poprawnie porównuje zestawy.

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
źródło
7

Druga odpowiedź będzie działać dobrze; tutaj jest inna alternatywa.

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

Należy jednak pamiętać, że to jest nie robić głębokie porównania równości. Więc

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

zwróci wartość false. Jeśli powyższe dwa zestawy mają być uznane za równe, musimy powtórzyć oba zestawy, wykonując głębokie porównania jakości dla każdego elementu. Zastrzegamy istnienie deepEqualrutyny. Wtedy logika byłaby taka

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

Co to robi: dla każdego członka s1 szukaj głęboko równego członka s2. Jeśli zostanie znaleziony, usuń go, aby nie można go było ponownie użyć. Te dwa zbiory są głęboko równe, jeśli wszystkie elementy w s1 znajdują się w s2, a s2 jest wyczerpane. Niesprawdzone.

Może się przydać: http://www.2ality.com/2015/01/es6-set-operations.html .


źródło
6

Żadne z tych rozwiązań nie przywraca oczekiwanej funkcjonalności do struktury danych, takiej jak zestaw zbiorów. W obecnym stanie zestaw JavaScript jest bezużyteczny do tego celu, ponieważ nadzbiór będzie zawierał zduplikowane podzbiory, które JavaScript błędnie postrzega jako odrębne. Jedynym rozwiązaniem, które przychodzi mi do głowy , jest konwersja każdego podzbioru na Array , sortowanie go, a następnie kodowanie jako String (na przykład JSON).

Rozwiązanie

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

Podstawowe użycie

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 ostateczny: zestaw zestawów

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
źródło
1
Świetne rozwiązanie! A jeśli wiesz, że właśnie masz zestaw ciągów lub liczb, po prostu staje się[...set1].sort().toString() === [...set2].sort().toString()
3

Powodem, dla którego twoje podejście zwraca fałsz, jest to, że porównujesz dwa różne obiekty (nawet jeśli mają tę samą zawartość), a zatem porównywanie dwóch różnych obiektów (nie odwołań, ale obiektów) zawsze zwraca błąd.

Poniższe podejście łączy dwa zestawy w jeden i po prostu głupio porównuje rozmiar. Jeśli jest taki sam, jest taki sam:

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

Upside : To bardzo proste i krótkie. Żadna zewnętrzna biblioteka, tylko zwykły JS

Wada : prawdopodobnie będzie to wolniejsze niż zwykłe iterowanie po wartościach i potrzebujesz więcej miejsca.

thadeuszlay
źródło
1

Porównanie dwóch obiektów za pomocą ==, ===

Kiedy używasz operatora ==or ===do porównania dwóch obiektów, zawsze otrzymasz, false chyba że te obiekty odnoszą się do tego samego obiektu . Na przykład:

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

W przeciwnym razie == równa się false, mimo że obiekt zawiera te same wartości:

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

Konieczne może być rozważenie ręcznego porównania

W ECMAScript 6 możesz wcześniej przekonwertować zestawy na tablice, abyś mógł zauważyć różnicę między nimi:

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

UWAGA: Array.from to jedna ze standardowych funkcji ECMAScript 6, ale nie jest szeroko obsługiwana w nowoczesnych przeglądarkach. Sprawdź tabelę zgodności tutaj: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from#Browser_compatibility

TaoPR
źródło
1
Czy to nie pozwoli zidentyfikować członka b których nie ma a?
1
@torazaburo Rzeczywiście. Najlepszym sposobem na pominięcie sprawdzania, czy bnie ama członków, jest sprawdzenie, czy a.size === b.size.
Aadit M Shah
1
Położyć na a.size === b.sizepierwszym miejscu zwarcie porównania poszczególnych elementów, jeśli nie jest to konieczne?
2
Jeśli rozmiar jest inny, z definicji zestawy nie są równe, więc lepiej najpierw sprawdzić ten warunek.
1
Cóż, inna kwestia polega na tym, że z natury zbiorów hasoperacje na zbiorach są zaprojektowane tak, aby były bardzo wydajne, w przeciwieństwie do indexOfoperacji na tablicach. Dlatego warto zmienić funkcję filtru na return !b.has(i). To również wyeliminowałoby potrzebę konwersji bna tablicę.
1

Utworzyłem szybki polyfill dla 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
źródło
1

Na podstawie zaakceptowanej odpowiedzi, zakładając poparcie Array.from, oto jedna linijka:

function eqSet(a, b) {
    return a.size === b.size && Array.from(a).every(b.has.bind(b));
}
John Hoffer
źródło
Albo prawdziwa jednolinijka zakładająca funkcje strzałek i operator spreadu: eqSet = (a,b) => a.size === b.size && [...a].every(b.has.bind(b))
John Hoffer,
1

Jeśli zbiory zawierają tylko pierwotne typy danych lub obiekty wewnątrz zbiorów mają równość odwołań, to jest prostszy sposób

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

Michaił Unenow
źródło
0

W testach stosuję to podejście:

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
źródło
5
Chwileczkę ... sprawdza tylko, czy A ma elementy, których B nie ma? Nie sprawdza, czy B ma elementy, których A nie ma. Jeśli spróbujesz a=[1,2,3]i b=[1,2,3,4], zobaczysz , że są takie same. Więc myślę, że potrzebujesz dodatkowego czeku, takiego jaksetA.size === setB.size
0

Bardzo niewielka modyfikacja oparta na odpowiedzi @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
);

Jeśli ktoś inny ma problem, tak jak ja, z powodu jakiegoś dziwactwa najnowszego babel, musiał dodać tutaj wyraźny warunek.

(Również w przypadku liczby mnogiej myślę, że areczytanie na głos jest nieco bardziej intuicyjne 🙃)

rob2d
źródło
-1

1) Sprawdź, czy rozmiary są równe. Jeśli nie, to nie są równi.

2) iteruj po każdym elemencie A i sprawdź, który istnieje w B. Jeśli jeden z nich się nie powiedzie, wróć unequal

3) Jeśli powyższe 2 warunki nie spełnią się, oznacza to, że są równe.

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) Metoda 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
źródło
Ta odpowiedź jest całkowicie błędna. Instrukcja return w forEachmetodzie NIE spowoduje powrotu funkcji nadrzędnej.
xaviert