≪ Today I learned.
RSS購読
    公開日
    タグ
    JavaScript
    著者
    ダーシノ

    集合メソッドを使った検索処理の最適化

    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()を使う。また、isSupersetOfisSubsetOfは対の関係なので、入れ替えることもできる。

    // 検索条件
    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))
    item.fruitstargetFruits
    fruits.isSupersetOf(targetFruits)

    OR条件

    Arrayメソッドをつかった場合

    Arrayメソッドを使う場合は、Array#some()Array#includes()のメソッドを使う。AND条件よりも実装は簡単で、everysomeを入れ替えるだけでよい。

    // 検索条件
    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))
    item.fruitstargetFruits
    fruits.isDisjointFrom(targetFruits) === false