LoginSignup
117
64
お題は不問!Qiita Engineer Festa 2024で記事投稿!
Qiita Engineer Festa20242024年7月17日まで開催中!

【JavaScript】ネイティブで集合演算できるようになった

Last updated at Posted at 2024-07-01

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ですね。

01.png

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です。

02.png

並び順は、最初の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}

差集合です。

03.png

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です。

04.png

でもドキュメントでは一貫して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

部分集合です。

05.png

要素が空集合だったり、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()の逆ですね。

06.png

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

図を見てのとおりです。

07.png

その他

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行もしない程度であり、集合演算の自力実装自体は全く難しくなかったわけですが、ネイティブ実装されることによって大きな高速化と普及が見込めることでしょう。

117
64
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
117
64