ECMA6セットの同等性の比較


102

2つのJavaScriptセットをどのように比較しますか?私は使っ==てみました===がどちらも偽を返します。

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

これらの2つのセットは同等です。定義上、セットには順序がないためです(少なくとも通常は)。Set on MDNのドキュメントを調べたところ何も役に立たないことがわかりました。誰でもこれを行う方法を知っていますか?


2つのセットは2つの異なるオブジェクトです。===値の等価性のためであり、オブジェクトの等価性のためではありません。
elclanrs 2015年

3
繰り返し、各メンバーの値を比較します。すべて同じ場合、セットは「同じ」です
dandavis '30

1
@dandavisセットでは、メンバー値です。

2
:何らかの理由で-セットとマップが挿入順であるため、持っているdeveloper.mozilla.org/en-US/docs/Web/JavaScript/Reference/...
CodeManX

8
最悪の場合でもnew Set([1,2,3]) != new Set([1,2,3])。これはJavascriptを作るセットのために役に立たないセットのセットスーパーセットが重複したサブセットが含まれていますので。思い浮かぶ唯一の回避策は、すべてのサブセットを配列に変換し、各配列を並べ替えてから、各配列を文字列(JSONなど)としてエンコードすることです。
7vujy0f0hy

回答:


73

これを試して:

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

より機能的なアプローチは次のとおりです。

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

このall関数は、すべての反復可能なオブジェクト(SetおよびなどMap)に対して機能します。

Array.fromより広くサポートされている場合は、次のようにall関数を実装できます。

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

お役に立てば幸いです。


2
名前hasを「isPartOfor isInor or」に変更する必要があると思いますelem
Bergi

@Bergi関数の名前をに変更しhasましたisIn
Aadit M Shah 2015

1
@DavidGivenはい、JavaScriptでのセットは、挿入順に反復されていますdeveloper.mozilla.org/en-US/docs/Web/JavaScript/Reference/...
Aadit Mシャー

48
JavaScriptがこれまでに作成された中で最もお粗末な言語であることを、私は毎日確信しています。誰もがその制限に対処するために独自の基本機能を発明する必要があり、これはES6にあり、私たちは2017年にいます!このような頻繁な関数をSetオブジェクトの仕様に追加できないのはなぜですか。
Ghasan 2017年

2
GhasanAl-Sakkaf @、私は...それはTC39は、科学者で構成されていることかもしれないですが、ない現実主義、合意
Marecky

59

あなたも試すことができます:

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


if条件に適合するため、間違いなく優れたソリューション
Hugodby 2017

このソリューションがどれほど慣用的で、どれほど読みやすいかが大好きです。ありがとう@Max
daydreamer

29

lodash_.isEqual()詳細な比較を行うを提供します。これは、独自に作成したくない場合に非常に便利です。lodash 4以降、_.isEqual()セットを適切に比較します。

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

他の答えはうまくいきます。ここに別の選択肢があります。

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

ただし、これは深い等価比較を行わないことに注意してください。そう

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

falseを返します。上記の2つのセットが等しいと見なされる場合は、両方のセットを繰り返し処理して、各要素で詳細な品質比較を行う必要があります。deepEqualルーティンの存在を規定しています。次に、ロジックは

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

これが行うこと:s1の各メンバーについて、s2の深く等しいメンバーを探します。見つかった場合は削除して、再度使用できないようにします。s1のすべての要素がs2にあり、 s2が使い果たされている場合、2つのセットは深く等しくなります。未テスト。

これは便利な場合があります:http : //www.2ality.com/2015/01/es6-set-operations.html


6

これらのソリューションはいずれも、セットのセットなどのデータ構造に期待される機能を「取り戻す」ことはできません現在の状態では、スーパーセットに重複するサブセットが含まれているため、Javascript セットはこの目的には役に立ちません。私が考えることができる唯一の解決策は、各サブセットをArrayに変換し、それを並べ替えてから、文字列としてエンコードすることです(たとえばJSON)。

解決

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

基本的な使い方

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>

究極のテスト:セットのセット

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
素晴らしい解決策!そして、文字列または数値のセットを取得したことがわかっている場合、それは次のようになります[...set1].sort().toString() === [...set2].sort().toString()

3

アプローチがfalseを返す理由は、2つの異なるオブジェクトを比較しているためです(たとえ同じコンテンツを取得したとしても)、2つの異なるオブジェクト(参照ではなくオブジェクト)を比較すると、常に偽の結果が返されます。

次のアプローチでは、2つのセットを1つにマージし、愚かにサイズを比較します。同じであれば、同じです。

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

メリット:とてもシンプルで短い。外部ライブラリなし、バニラJSのみ

欠点:値を繰り返し処理するよりも遅くなり、より多くのスペースが必要になります。


1

==、===で2つのオブジェクトを比較する

==or ===演算子を使用して2つのオブジェクトを比較するfalse 場合、それらのオブジェクトが同じオブジェクトを参照しない限り、常に取得されます。例えば:

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

それ以外の場合、オブジェクトに同じ値が含まれていても、==はfalseと同等です。

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

手動で比較する必要があるかもしれません

ECMAScript 6では、事前にセットを配列に変換して、それらの違いを見つけることができます。

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

注: Array.fromは標準のECMAScript 6機能の1つですが、最新のブラウザでは広くサポートされていません。こちらの互換性テーブルを確認してください:https : //developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from#Browser_compatibility


1
bこれは、含まれていないメンバーを識別できないのではないaですか?

1
@torazaburoなるほど。のメンバーbがいないかどうかのチェックをスキップする最良の方法aは、かどうかをチェックすることa.size === b.sizeです。
Aadit M Shah 2015

1
置くa.size === b.size必要がない場合は、短絡に最初の個々の要素の比較を?

2
サイズが異なる場合、定義上、セットは等しくないため、最初にその状態を確認することをお勧めします。

1
さて、ここでの他の問題は、セットの性質上、セットでのhas操作はindexOf配列での操作とは異なり、非常に効率的になるように設計されていることです。したがって、フィルター関数をに変更することは理にかなっていますreturn !b.has(i)。またb、配列に変換する必要もなくなります。

1

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

受け入れられた回答に基づいて、のサポートを想定してArray.from、ここにワンライナーがあります:

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

または、矢印関数とスプレッド演算子を想定した真のワンライナー: eqSet = (a,b) => a.size === b.size && [...a].every(b.has.bind(b))
John Hoffer 2018年

1

セットにプリミティブデータ型のみが含まれている場合、またはセット内のオブジェクトに参照等価性がある場合、より簡単な方法があります。

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


0

私はテストでこのアプローチに従います:

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
ちょっと待ってください...これは、AにBにはない要素があるかどうかをチェックするだけですか?BにAにはない要素があるかどうかはチェックしません。とを試してみるa=[1,2,3]b=[1,2,3,4]、同じであると表示されます。私が推測するように、次のような余分なチェック必要setA.size === setB.size

0

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

他の誰かが最新のバベルの癖のために私がしたように問題を抱えている場合、ここに明示的な条件を追加する必要がありました。

(複数形の場合も、are読み上げる方が少し直感的だと思います🙃)


-1

1)サイズが等しいかどうかを確認します。そうでない場合、それらは等しくありません。

2)Aの各elemを反復処理し、Bに存在することを確認します。1つ失敗した場合はreturn unequal

3)上記の2つの条件が満たされない場合、それらは等しいことを意味します。

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


この答えは完全に間違っています。forEachメソッドのreturnステートメントは、親関数を返しません。
xaviert
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.