2022-08-14に更新

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

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

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMapこちらのページの「map()のアイテムの追加と削除」の項目にある通り、処理の中で空配列([])を返すと、その要素の処理を削除することができるようです。

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/flatMap

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

・・・で、これで終わるとしょうもないので、Rubyで同様のことができるのかやってみました。https://docs.ruby-lang.org/ja/latest/method/Enumerable/i/collect_concat.html

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などをかけた場合の処理時間の差を見てみました。

今回はここまで。おわり

Originally published at shikiyura.com
ツイッターでシェア
みんなに共有、忘れないようにメモ

しきゆら

勉強したり手を動かした記録を「しきゆらの備忘録」(http://shikiyura.com)へ投稿している人。 Ruby/JavaScriptをよく書いている。いろんな言語に触れてみたい。新しい物・辛いもの好き。バグは愛すべきもの。一応社会人。

Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。

また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!

有料記事を販売できるようになりました!

こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?

コメント