membandingkan set ECMA6 untuk kesetaraan

103

Bagaimana Anda membandingkan dua set javascript? Saya mencoba menggunakan ==dan ===tetapi keduanya mengembalikan false.

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

Kedua himpunan ini ekuivalen, karena menurut definisi, himpunan tidak memiliki urutan (setidaknya biasanya tidak). Saya telah melihat dokumentasi untuk Set di MDN dan tidak menemukan apa pun yang berguna. Ada yang tahu bagaimana melakukan ini?

kode william
sumber
Dua set adalah dua objek yang berbeda. ===adalah untuk persamaan nilai, bukan persamaan objek.
elclanrs
3
mengulangi dan membandingkan nilai setiap anggota, jika semua sama, set "sama"
dandavis
1
@dandavis Dengan set, anggota adalah nilai.
2
Sets dan Maps memang memiliki urutan, yaitu urutan penyisipan - untuk alasan apa pun: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
CodeManX
8
Bahkan yang terburuk new Set([1,2,3]) != new Set([1,2,3]). Hal ini membuat Javascript Set tidak berguna untuk set set karena superset akan berisi subset duplikat. Satu-satunya solusi yang muncul dalam pikiran adalah mengonversi semua himpunan bagian menjadi larik, mengurutkan setiap larik, lalu mengenkode setiap larik sebagai string (misalnya JSON).
7vujy0f0hy

Jawaban:

74

Coba ini:

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

Pendekatan yang lebih fungsional adalah:

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

The allkarya fungsi untuk semua objek iterable (misalnya SetdanMap ).

Jika Array.fromlebih banyak didukung maka kita bisa mengimplementasikan allfungsi sebagai:

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

Semoga membantu.

Aadit M Shah
sumber
2
Saya pikir Anda harus mengubah nama hasmenjadi isPartOfatau isInatauelem
Bergi
1
@DavidGiven Ya, set dalam JavaScript diiterasi dalam urutan penyisipan: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Aadit M Shah
49
Setiap hari saya lebih yakin JavaScript menjadi bahasa paling buruk yang pernah dibuat. Setiap orang harus menemukan fungsi dasarnya sendiri untuk mengatasi keterbatasannya, dan ini ada di ES6, dan kita ada di tahun 2017! Mengapa mereka tidak bisa menambahkan fungsi sesering itu ke spesifikasi objek Set!
Ghasan
2
@ GhasanAl-Sakkaf, saya setuju, mungkin TC39 terdiri dari ilmuwan, tetapi tidak ada pragmatis ...
Marecky
1
@TobiasFeil Haskell bahkan lebih baik.
Aadit M Shah
60

Anda juga dapat mencoba:

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
sumber
Jelas merupakan solusi yang lebih baik karena cocok dengan kondisi if
Hugodby
Saya suka betapa idiomatisnya solusi ini, dan seberapa mudah dibaca! Terima kasih @Max
pelamun
29

lodash menyediakan _.isEqual(), yang melakukan perbandingan mendalam. Ini sangat berguna jika Anda tidak ingin menulis sendiri. Pada lodash 4, _.isEqual()membandingkan Set dengan benar.

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
antoniserius
sumber
7

Jawaban lain akan bekerja dengan baik; ini alternatif lain.

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

Namun, ketahuilah bahwa ini tidak melakukan perbandingan kesetaraan yang dalam. Begitu

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

akan mengembalikan false. Jika dua set di atas dianggap sama, kita perlu mengulang melalui kedua set tersebut dengan melakukan perbandingan kualitas yang mendalam pada setiap elemen. Kami menetapkan adanya deepEqualrutinitas. Maka logikanya akan seperti itu

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

Apa fungsinya: untuk setiap anggota s1, cari anggota s2 yang sangat setara. Jika ditemukan, hapus agar tidak dapat digunakan lagi. Kedua himpunan tersebut sangat sama jika semua elemen di s1 ditemukan di s2, dan s2 habis. Belum dicoba.

Anda mungkin menemukan ini berguna: http://www.2ality.com/2015/01/es6-set-operations.html .


sumber
6

Tak satu pun dari solusi ini membawa "kembali" fungsionalitas yang diharapkan ke struktur data seperti kumpulan kumpulan. Dalam keadaannya saat ini, Javascript Set tidak berguna untuk tujuan ini karena superset akan berisi subset duplikat, yang secara keliru dianggap Javascript sebagai perbedaan. Satu-satunya solusi yang dapat saya pikirkan adalah mengubah setiap subset menjadi Array , mengurutkannya dan kemudian mengkodekannya sebagai String (misalnya JSON).

Larutan

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

Penggunaan dasar

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>

Tes terakhir: set 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>

7vujy0f0hy
sumber
1
Solusi bagus! Dan jika Anda tahu bahwa Anda baru saja mendapatkan satu set string atau angka, itu hanya menjadi[...set1].sort().toString() === [...set2].sort().toString()
3

Alasan mengapa pendekatan Anda mengembalikan nilai salah adalah karena Anda membandingkan dua objek yang berbeda (meskipun keduanya memiliki konten yang sama), sehingga membandingkan dua objek yang berbeda (bukan referensi, tetapi objek) selalu menghasilkan kesalahan.

Pendekatan berikut menggabungkan dua set menjadi satu dan dengan bodohnya membandingkan ukurannya. Jika sama, berarti sama:

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

Terbalik : Sangat sederhana dan pendek. Tidak ada perpustakaan eksternal hanya vanilla JS

Kelemahan : Ini mungkin akan menjadi lebih lambat daripada hanya mengulang nilai dan Anda membutuhkan lebih banyak ruang.

thadeuszlay
sumber
1

Membandingkan dua objek dengan ==, ===

Saat menggunakan operator ==or ===untuk membandingkan dua objek, Anda akan selalu mendapatkan false kecuali objek tersebut mereferensikan objek yang sama . Sebagai contoh:

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

Jika tidak, == sama dengan false meskipun objek tersebut berisi nilai yang sama:

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

Anda mungkin perlu mempertimbangkan perbandingan manual

Di ECMAScript 6, Anda dapat mengonversi set ke array sebelumnya sehingga Anda dapat melihat perbedaan di antara mereka:

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

CATATAN: Array.from adalah salah satu fitur ECMAScript 6 standar tetapi tidak didukung secara luas di browser modern. Periksa tabel kompatibilitas di sini: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from#Browser_compatibility

TaoPR
sumber
1
Bukankah ini akan gagal untuk mengidentifikasi anggota byang tidak termasuk a?
1
@torazaburo Memang. Cara terbaik untuk melewati pemeriksaan apakah anggota btidak masuk aadalah dengan memeriksa apakah a.size === b.size.
Aadit M Shah
1
a.size === b.sizePertama-tama, buat hubungan pendek perbandingan elemen-elemen individu jika tidak perlu?
2
Jika ukurannya berbeda, menurut definisi himpunannya tidak sama, jadi sebaiknya periksa dulu kondisinya.
1
Masalah lainnya di sini adalah sifat set, hasoperasi pada set dirancang untuk menjadi sangat efisien, tidak seperti indexOfoperasi pada array. Oleh karena itu, masuk akal untuk mengubah fungsi filter Anda menjadi return !b.has(i). Itu juga akan menghilangkan kebutuhan untuk mengubahnya bmenjadi sebuah array.
1

Saya membuat polyfill cepat untuk 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
sumber
1

Berdasarkan jawaban yang diterima, dengan asumsi dukungan Array.from, berikut adalah satu kalimat:

function eqSet(a, b) {
    return a.size === b.size && Array.from(a).every(b.has.bind(b));
}
John Hoffer
sumber
Atau one-liner yang benar dengan asumsi fungsi panah dan operator penyebaran: eqSet = (a,b) => a.size === b.size && [...a].every(b.has.bind(b))
John Hoffer
1

Jika kumpulan hanya berisi tipe data primitif atau objek di dalam kumpulan memiliki persamaan referensi, maka ada cara yang lebih sederhana

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

Mikhail Unenov
sumber
0

Saya mengikuti pendekatan ini dalam tes:

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
sumber
5
Tunggu sebentar ... ini hanya memeriksa apakah A memiliki elemen yang tidak dimiliki B? Itu tidak memeriksa apakah B memiliki elemen yang A tidak. Jika Anda mencoba a=[1,2,3]dan b=[1,2,3,4], maka dikatakan sama. Jadi saya rasa Anda perlu pemeriksaan tambahan sepertisetA.size === setB.size
0

Modifikasi yang sangat sedikit berdasarkan jawaban @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
);

Jika ada orang lain yang mengalami masalah seperti yang saya lakukan karena beberapa kekhasan dari babel terbaru, harus menambahkan persyaratan eksplisit di sini.

(Juga untuk jamak menurut saya arehanya sedikit lebih intuitif untuk dibaca dengan keras 🙃)

rob2d
sumber
-1

1) Periksa apakah ukurannya sama. Jika tidak, maka mereka tidak sama.

2) ulangi setiap elem A dan periksa yang ada di B. Jika salah satu gagal kembali unequal

3) Jika 2 kondisi di atas gagal berarti sama.

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) Metode 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
sumber
Jawaban ini sepenuhnya salah. Pernyataan return dalam forEachmetode TIDAK akan membuat fungsi induk kembali.
xaviert