【JS】flatMapで不要なものを削除したい

2022年8月14日JavaScript,Ruby

こんにちは、しきゆらです。
今回は、flatMapの処理の中で不要な要素が出てきた場合にそれを排除する方法を知ったのでメモしておきます。

結論: 空配列([])を返すと削除できる

こちらのページにある通り、
処理の中で空配列([])を返すと、その要素の処理を削除することができるようです。

let array = [1,2,3,4,5,6,7,8];
array.flatMap( (item) => {
    if (item % 2 === 0) {
        return item * 2;
    } else {
        return [];
    }
})
// => [4, 8, 12, 16]

なお、同様の処理をmapで行うと、そのまま要素が空配列に置き換わってしまいました。(それはそう)

let array = [1,2,3,4,5,6,7,8];
array.map( (item) => {
    if (item % 2 === 0) {
        return item * 2;
    } else {
        return [];
    }
})
// =>[Array(0), 4, Array(0), 8, Array(0), 12, Array(0), 16]

ということで、flatMapの場合は処理の中で不要な要素を削除ができるようです。

なぜ、flatMapで空配列を返すと要素を削除することができるのかというのは

flatMap()map()の後にflat()を行うのと同じ

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/flatM

とある通り、処理の中でflat()をかけるので空配列は消えてしまいます。
ということで、flatMapの中で空配列を返してあげれば要素を削除することができるよ、という話でした。

・・・で、これで終わるとしょうもないので、Rubyで同様のことができるのかやってみました。

irb(main):001:0> a = [1,2,3]
=> [1, 2, 3]
irb(main):002:0> b = [-1, 0, 9]
=> [-1, 0, 9]
irb(main):003:0> list = [a, b]
=> [[1, 2, 3], [-1, 0, 9]]
irb(main):004:0> list.flat_map{|array| array.select{|i| i < 0}}
=> [-1]
irb(main):005:0> list.flat_map{|array| array.include?(-1)? []: array}
=> [1, 2, 3]

こちらも同様で、空配列を返すと削除される動きをしています。
ただ、リファレンスにはそのような記載はないので、当たり前だよね、として書かれていないのかなと。

不要な要素をはじく場合、flatMapとmap+αはどっちが速いのか

この手の処理はmap()メソッドの前後で特定の値をはじいたりしていましたが、どちらが速いのでしょう。
気になったので、ついでに調べてみました。
ここでもJSとRubyで測ってみました。

例としては微妙ですが、-5から5の間の乱数の中で0以上の場合に3倍する処理をflatMapと他の処理で処理時間を計測してみます。

JSの場合

こんな感じの雑さで試してみます。

なお、処理の中で不要なコードが混じってますが
一応2つの処理で同じ結果が返ってくるかを確認したかったのでequalArrayという雑チェック関数を作ってます。

import * as Benchmark from 'benchmark';

const filterMapTest = (array) => {
    return array.filter((item) => {
        return item >= 0;
    }).map(item => item*3)
}

const flatMapTest = (array) => {
    return array.flatMap((item) => {
        if(item >= 0) {
            return item * 3;
        } else {
            return [];

        }
    })
}

const equalArray = (a, b) => {
    if(!Array.isArray(a)) return false;
    if(!Array.isArray(b)) return false;
    if(a.length !== b.length) return false;

    for(let i = 0; i < a.length; i += 1) {
        if(a[i] !== b[i]) return false;
    }

    return true;
}

// initialize
const minNum = -5;
const maxNum = 5;
const array = [...Array(99*99)].map(_ => Math.floor(Math.random() * 10) - 5);
let suite = new Benchmark.Suite

// benchmark
suite
    .add("filter + map", () => {
        filterMapTest(array)
    })
    .add("flatMap", () => {
        flatMapTest(array)
    })
    .on('cycle', (event) => {
        console.log(String(event.target))
    })
    .on('complete', function() {
        console.log('Fastest is ' + this.filter('fastest').map('name'));
    })
    .run({'async': true})

const filterTest = filterMapTest(array);
const flatTest = flatMapTest(array);


console.log(equalArray(filterTest, flatTest))

実行結果はこんな感じでした。

$ > node dist/bench.js
true
filter + map x 21,538 ops/sec ±18.58% (87 runs sampled)
flatMap x 1,673 ops/sec ±0.93% (96 runs sampled)
Fastest is filter + map

大きな差はなさそうですが、flatMapよりもfilter+mapのほうが速そうです。

Rubyの場合

こんな感じの雑さで試してみました。

require "benchmark"

def map_uniq(array)
  array.map { |item| item.negative? ? next : item * 3}.compact
end

def flatmap(array)
  array.flat_map{|item| item.negative? ? []: item * 3 }
end

array = Array.new(99*99){ rand(-5...5) }

Benchmark.bmbm do |r|
  r.report("flatMap") { flatmap(array) }
  r.report("map_uniq") { map_uniq(array) }
end

実行結果はこんな感じでした。

Rehearsal --------------------------------------------
flatMap    0.001012   0.000000   0.001012 (  0.001012)
map_uniq   0.000562   0.000000   0.000562 (  0.000562)
----------------------------------- total: 0.001574sec

               user     system      total        real
flatMap    0.000815   0.000000   0.000815 (  0.000814)
map_uniq   0.000564   0.000000   0.000564 (  0.000563)

よく考えなくても、flatMapのほうは無駄に配列生成して処理スキップさせているので遅いよなぁ、という印象でした。

まとめ

今回は、JSのflatMapで不要な要素をはじいて処理することができる、ということを知ったのでメモしました。
また、気になったので、同様な処理がRubyでもできるか確認したり、flatMapで不要要素を弾く場合とfilterなどをかけた場合の処理時間の差を見てみました。

今回はここまで。
おわり

Posted by しきゆら