やわらかテック

業務を通して得られた知見、知見。個人的に試してみたこと、興味のあることについてアウトプットしています。

JavaScriptで連番の配列を生成する4つの方法と速度比較

連番の生成に関して

意外と使用する頻度が高かったりします。他言語であればRange(eg: 1..10)のようなClassやデータ構造が定義されており、簡単に連番の配列を作成することが出来ます。しかし、JavaScript には連番の配列を簡単に生成するための機能が提供されていないので、一工夫してあげる必要があります。色々と試して結果、4つの方法を思いつきました。内、1つはQiitaで紹介されたいた書き方なので元記事のURLを貼っておきます。

以下、5000回の実行速度の平均速度の速かった順番に書き方を紹介していきます。実行速度の測定は実行開始前と実行終了後の単純な時間差を見ています。測定に使用したコードがこちらです。

const verificater = (execFunc, totalExec=5000) => {
    const result = mesureAverageElapsedTime(execFunc, totalExec);
    console.log(`Result: ${result} ms / ${execFunc.toString()}`);
}

const mesureAverageElapsedTime = (execFunc, totalExec) => {
    const trylst = range(0, totalExec);
    const totalTime = trylst.reduce((acc, _) => mesureElapsedTime(execFunc) + acc);
    return totalTime / totalExec;
}

const mesureElapsedTime = (execFunc) => {
    const startTime = Date.now();
    execFunc()
    const endTime = Date.now();
    return endTime - startTime
}

4つの方法

測定結果:

Result: 0.0006 ms /  5000, () => forRange(0, 31)
Result: 0.0012 ms / 5000, () => arrayGen(31)
Result: 0.0022 ms / 5000, () => arrayFromGen(31)
Result: 0.0128 ms / 5000, () => range(0, 31)

なお、実測を平等に行うために、全て関数として定義しています。

第1位: forを使った生成(forRange)

Result: 0.0006 ms / 5000
シンプルにfor文を使った方法です。事前に定義した空配列に値を追加していきます。

const forRange = (a, z) => {
    const lst = [];
    for (let i = a; i <= z; i++) {
        lst.push(i)
    }
    return lst;
};

第2位: Qiitaで紹介されていた方法(arrayGen)

0.0012 ms / 5000

Qiitaで紹介されていた今回、最もスマートな記述です。かっこいいですね🎉

qiita.com

const arrayGen = z => [...Array(z+1)].map((_, i) => i);

第3位: Array.fromを使った方法(arrayGenFrom)

Result: 0.0022 ms / 5000

過去のプロジェクトで自分が使った方法です。Qiitaで紹介されていた方法の劣化版ですね。速度も遅く、使う理由はあまりないかなと思います。

const arrayFromGen = z => Array.from(Array(z+1), (_, i) => i);

第4位: 再帰関数を使った方法(range(0, 31))

Result: 0.0128 ms / 5000

再帰関数で書けないかなと思って、無理矢理書いた方法です。スッキリさと裏腹に可読性も悪く、速度も悪いという残念な結果となってしまいました。

const range = (a, z) => _range(a, z, [])
const _range = (a, z, acc) => a < z + 1 ? _range(a+1, z, acc.concat(a)) : acc

結論

多分これが一番早いと思います。

const forRange = (a, z) => {
    const lst = [];
    for (let i = a; i <= z; i++) {
        lst.push(i)
    }
    return lst;
};

JavaScriptを書く人はfor文を異常に避ける方がいますが、この程度の処理だったら、for文で全然良いのかなと思います。 参考になればと思います。

追記

Result: 0.0132 ms / () => range(0, 31)
Result: 0.0012 ms / () => neoRange(0, 31)

再帰関数を三項演算子を使わずに、concatからpushに乗り換えて実装してみたら、めちゃくちゃ早くなりました。どうやら、concatの処理が毎回、新しく配列を生成するので、かなりのボトルネックになっていたようです。

const neoRange = (a, z) => _neoRange(a, z, []);
const _neoRange = (a, z, acc) => {
    if (a < z + 1) {
        acc.push(a)
        // const next = acc.concat(a)
        return _neoRange(a + 1, z, acc)
    } else {
        return acc;
    }
}