JavaScriptにはだいぶ前からSetオブジェクトがありましたが、何故か集合演算は全く定義されておらず自力で実装しなければなりませんでした。
その後、まあ不便だねってことでSet Methods for JavaScriptというproposalが提出されました。
実装は珍しくSafariが最も早く、2023/09/18のSafari17から対応しました。
その後2024/02/21にChrome122、そして2024/06/11にFirefox127で実装されたことにより、主要全ブラウザで集合演算が使用可能になりました。
複数環境で実装されたことから、無事ES2025としてStage4、つまり上がりになりました。
ということで使い方を紹介するよ。
Set.prototype.intersection()
要素と引数の、両方に含まれる値を返します。
new Set([1, 2, 3, 4]).intersection( new Set([1, 3, 5])); // Set{1, 3}
new Set([1, 2, 3, 4]).intersection( new Set([5, 6, 7])); // Set{}
いわゆるANDですね。
Set.prototype.union()
要素と引数の、少なくともどちらか片方に含まれる値を返します。
new Set([1, 2, 3]).union( new Set([1, 3, 5])); // Set{1, 2, 3, 5}
new Set([1, 2, 3]).union( new Set([4, 5, 6])); // Set{1, 2, 3, 4, 5, 6}
こちらはORです。
並び順は、最初のSetのあとに後ろのSetの要素が追加されます。
Setなので順番は保証されています。
Set.prototype.difference()
要素のうち、引数に含まれないものを返します。
new Set([1, 2, 3]).difference( new Set([1, 3, 5])); // Set{2}
new Set([1, 2, 3]).difference( new Set([4, 5, 6])); // Set{1, 2, 3}
差集合です。
Set.prototype.symmetricDifference()
要素と引数の、どちらか片方にしか含まれない値を返します。
new Set([1, 2, 3]).symmetricDifference( new Set([1, 3, 5])); // Set{2, 5}
new Set([1, 2, 3]).symmetricDifference( new Set([4, 5, 6])); // Set{1, 2, 3, 4, 5, 6}
XORです。
でもドキュメントでは一貫してsymmetric difference対称差と書かれていて、exclusive disjunction排他的論理和とは書かれていないんですよね。
違いがわかんねえ。
Set.prototype.isSubsetOf()
要素が全て、引数に含まれる場合にtrueを返します。
new Set([1, 2, 3]).isSubsetOf( new Set([1, 2, 3, 4])); // true
new Set([1, 2, 3]).isSubsetOf( new Set([1, 2, 4, 5])); // false
new Set([1, 2, 3]).isSubsetOf( new Set([1, 2, 3])); // true
new Set().isSubsetOf( new Set([1, 2, 3])); // true
部分集合です。
要素が空集合だったり、A==Bだったりする場合もtrueになります。
Set.prototype.isSupersetOf()
引数が、要素に含まれる場合にtrueを返します。
new Set([1, 2, 3]).isSupersetOf( new Set([1, 2, 3, 4])); // false
new Set([1, 2, 3]).isSupersetOf( new Set([1, 2, 4, 5])); // false
new Set([1, 2, 3]).isSupersetOf( new Set([1, 2, 3])); // true
new Set([1, 2, 3]).isSupersetOf( new Set()); // true
要するにisSubsetOf()
の逆ですね。
Set.prototype.isDisjointFrom()
要素と引数に、一切の共通点がない場合にtrueを返します。
new Set([1, 2, 3]).isDisjointFrom(new Set([4, 5, 6])); // true
new Set([1, 2, 3]).isDisjointFrom(new Set([2, 5, 6])); // false
new Set([]).isDisjointFrom(new Set([])); // true
図を見てのとおりです。
その他
Mapとは演算が可能です。
ただし、比較に使われるのは値ではなくキーです。
const set = new Set([1, 2]);
const map = new Map([[1, 2], [3, 4]]);
set.intersection(map) // Set{1}
両方に含まれる値は2ではなく1になります。
かなり直感に反する。
またMap側には実装されていないので、map.intersection(set)
とはできません。
ところで、最も比較したくなるであろうArrayとは何故か演算できません。
const set = new Set([1, 2]);
const arr = new Array([1, 2]);
set.intersection(arr) // TypeError: The .size property is NaN
Setメソッドたちは、たとえ使わない場合でも必ず.size
・.keys
・.has
の3プロパティを参照します。
と、ドキュメントには書いてあります。
従って、これらを実装すれば自作クラスも比較に使えると思いますが、Arrayには.size
がないのでエラーになります。
重複をどうするか問題とか色々あるので難しいかもしれませんが、でもまあやっぱりArrayには対応してほしかったところですね。
感想
試しにクラスを自作してみたらnext()
メソッドがないって言われて動かなかった。
実装しても言われる。
よくわからない。
なお、これらの関数はずっと昔からpolyfillが公開されていました。
いずれも10行もしない程度であり、集合演算の自力実装自体は全く難しくなかったわけですが、ネイティブ実装されることによって大きな高速化と普及が見込めることでしょう。