集合メソッドを使った検索処理の最適化
ECMAScript Stage 4 になった仕様やES2025で追加される新しいSetメソッドを使ってfilter実装を簡素化するを実業務で使う機会があったので紹介する。
検索処理を実装する際、よくあるのが複数の条件を指定したときにAND検索にするか、OR検索にするか。その検索処理を実装するにはArray#every()やArray#some()、Array#includes()を使うのだが、2重ループになるためコードの可読性が落ちてしまう。
Arrayの代わりにSet#isSubsetOf()やSet#isSupersetOf()、Set#isDisjointFrom()のような集合メソッドを使うことでよりわかりやすく、高速に実装できる。
AND条件
Arrayメソッドをつかった場合
Arrayメソッドを使う場合は、Array#every()とArray#includes()のメソッドを使う。言語的にはitems.fruitsを主語にしたいが、実装するにしても構造的に難しい。
// 検索条件
const targetFruits = ['りんご', 'いちご']
// 検索対象
const items = [
{ fruits: ['りんご', 'いちご', 'みかん'] },
{ fruits: ['りんご', 'みかん', 'ぶどう'] },
{ fruits: ['いちご', 'みかん', 'ぶどう'] },
{ fruits: ['みかん', 'ぶどう'] },
]
// [ { fruits: ['りんご', 'いちご', 'みかん'] } ]
items.filter(item => (
// targetFruitsが主語になっている
targetFruits.every(target => (
item.fruits.includes(target)
))
))
Setの集合メソッドを使った場合
Setの集合メソッドを使う場合は、部分集合(または包含関係)を表すSet#isSupersetOf()やSet#isSubsetOf()を使う。また、isSupersetOfとisSubsetOfは対の関係なので、入れ替えることもできる。
// 検索条件
const targetFruits = new Set(['りんご', 'いちご'])
// 検索対象
const items = [
{ fruits: new Set(['りんご', 'いちご', 'みかん']) },
{ fruits: new Set(['りんご', 'みかん', 'ぶどう']) },
{ fruits: new Set(['いちご', 'みかん', 'ぶどう']) },
{ fruits: new Set(['みかん', 'ぶどう']) },
]
items.filter(item => item.fruits.isSupersetOf(targetFruits))
// 主語を入れ替えることも可能
items.filter(item => targetFruits.isSubsetOf(item.fruits))
fruits.isSupersetOf(targetFruits)OR条件
Arrayメソッドをつかった場合
Arrayメソッドを使う場合は、Array#some()とArray#includes()のメソッドを使う。AND条件よりも実装は簡単で、everyとsomeを入れ替えるだけでよい。
// 検索条件
const targetFruits = ['りんご', 'いちご']
// 検索対象
const items = [
{ fruits: ['りんご', 'いちご', 'みかん'] },
{ fruits: ['りんご', 'みかん', 'ぶどう'] },
{ fruits: ['いちご', 'みかん', 'ぶどう'] },
{ fruits: ['みかん', 'ぶどう'] },
]
/**
* [
* { fruits: ['りんご', 'いちご', 'みかん'] },
* { fruits: ['りんご', 'みかん', 'ぶどう'] },
* { fruits: ['いちご', 'みかん', 'ぶどう'] },
* ]
*/
items.filter(item => (
item.fruits.some(target => (
targetFruits.includes(target)
))
))
Setの集合メソッドを使った場合
Setの集合メソッドを使う場合は、空集合を表すSet#isDisjointFrom()を使う。OR条件の場合、Setオブジェクトでは論理型(boolean型)を返すメソッドがない。(※)そのため少なくともひとつは一致することを表現するため、isDisjointFrom()を反転して使用する。
※共通部分(Set#inersection())はあるが、boolean型ではなくSetオブジェクトを返すため、set.size > 0みたいな処理になる。
// 検索条件
const targetFruits = new Set(['りんご', 'いちご'])
// 検索対象
const items = [
{ fruits: new Set(['りんご', 'いちご', 'みかん']) },
{ fruits: new Set(['りんご', 'みかん', 'ぶどう']) },
{ fruits: new Set(['いちご', 'みかん', 'ぶどう']) },
{ fruits: new Set(['みかん', 'ぶどう']) },
]
items.filter(item => item.fruits.isDisjointFrom(targetFruits) === false)
// 主語を入れ替えることも可能
items.filter(item => targetFruits.isSubsetOf(item.fruits))
fruits.isDisjointFrom(targetFruits) === false