JavaScriptで集合演算の記事に @miumiu0917 さんから、JavaScriptのSetはユーザ定義なオブジェクトが扱えないとコメントを頂いた。
たしかに、MDN Setの冒頭にSetはプリミティブかオブジェクトの参照を対象にしていると書いてあった。
The Set object lets you store unique values of any type, whether primitive values or object references.
例えば、配列を要素とするSetでは、配列の実体をそのまま入れると、配列の要素の値が同じでも別物として扱われてしまう。
arrayA = [1,2];
arrayB = [3,4];
set1 = new Set([arrayA, arrayB]);
set2 = new Set([arrayA]);
set3 = new Set([[1,2]]);
set2.isSubset(set1);
// true
set3.isSubset(set1);
// false
※ isSubsetについてはJavaScriptで集合演算を御覧ください。
内容で要素を検証するSet
Setを拡張すれば、配列やユーザ定義のオブジェクトの持つ値で要素を検証するようにできるのでは、と思ったのでやってみた。
ContentSetという名前でSetを継承したクラスを作ってみた。変えたのは has
と add
。
has
は親クラスの has
で true
ならそのままで、 false
なら自分の要素を操作して一致しているかを検証するコードを足した。
JavaScriptは演算子の上書きができないみたいなので、値の同一性をどうやって検証しようかちょっと悩んだ。JavaScriptで配列やオブジェクトを比較して等しいかチェックする方法で、一旦JSONでシリアライズして比較しているのを見つけ、それに倣った。
add
は拡張した has
で検証してから値を足すようにした。
class ContentSet extends Set {
has(value){
if(super.has(value)){
return true;
}
for (var elem of this) {
if (JSON.stringify(value) === JSON.stringify(elem)) {
return true;
}
}
return false;
}
add(value){
if(this.has(value)){
return;
}
super.add(value);
}
}
ChromeとFirefoxのJavaScript Consoleで実行してみたけど、期待どおりに動いているみたい。
a = new ContentSet([[1]]);
// Set(1) {Array(1)}
a.has([1]);
// true
a.add([1]);
// undefined
a
// Set(1) {Array(1)}
a.size;
// 1
b = new ContentSet([[1], [1]]);
// Set(1) {Array(1)}
b
// Set(1) {Array(1)}
冒頭の例をContentSetで実行してみたけど、こちらも期待どおり。(Chromeで確認)
arrayA = [1,2];
arrayB = [3,4];
set1 = new ContentSet([arrayA, arrayB]);
set2 = new ContentSet([arrayA]);
set3 = new ContentSet([[1,2]]);
set2.isSubset(set1);
// true
set3.isSubset(set1);
// true
感想
- JSONでシリアライズして文字列として比較しているので、オブジェクトの要素の順序とか大丈夫なのかよくわからないけど、とりあえず動いている。
- 毎回シリアライズするので遅そう。
- コンストラクタとかはとくに変えてないけど、挙動が変わっているということは、ちゃんと自分の
has
を使ってくれているのだろうと思う。 - それなら
add
も変えなくてよさそうだけど、それだと要素が重複してしまったので必要な改変だったようだ。
JavaScriptは素人なので、間違っているかもしれないけど、ご参考まで。