【JavaScript】range(整数列)の作成方法

2021-06-01

所定の回数だけインデクスに応じて何か処理をしてそれぞれの結果を配列にしたいという処理をサクッと書きたいんだけど、 JavaScriptで整数配列を簡単に生成する方法が覚えられなくて毎度ウェブ検索のお世話になっていたので調べた。

結論

スプレッドしてmap [...Array(N)].map((_, i) => i) が簡潔さと速度の面でよいと思う。

.

.

末尾にベンチマークの実行あり)

前書き

ループを自前で回せば一応できるんだけど、for文を書くのが面倒なのでサクッとmapしたい。 Pythonではrange、Rubyでは...Rangeオブジェクトなるものが簡単に生成できる。 JavaScriptではそのようなものは無いようで、自作する必要がある。

候補

実際には整数列を加工した結果を得たいので、rangeの代替を作るだけじゃなくて実際になにか計算させてみる(ここでは自乗i * iとしてみた)。 何回か実行して処理にかかる経過時間を performance.now() で取得して平均を測る。

for-push:愚直にforループを書いて、結果をpush

const array = new Array()
for (let i = 0; i < N; ++i)
array.push(i * i)

prealloc-for-store:容量を指定して配列を確保、愚直にforループを書いて結果を格納

const array = new Array(N)
for (let i = 0; i < N; ++i)
array[i] = i * i

array.push.map:配列にpushして整数列を生成し、mapする

function array_push(n) {
const array = new Array()
for (let i = 0; i < n; ++i)
array.push(i)
return array
}

array_push(N).map(i => i * i)

array.prealloc.map:容量を指定して配列を確保・格納してmap

function array_prealloc(n) {
const array = new Array(n)
for (let i = 0; i < n; ++i)
array[i] = i
return array
}

array_prealloc(N).map(i => i * i)

array.from.map:Array.fromを使う

Array(N) または new Array(N) で容量を指定して配列が確保できるが、それに対して map を使っても意図した結果にならない:

Array(5).map((v, i) => i)  //=> [ <5 empty items> ]

なんか謎の挙動なんだけど、

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/Array 注: これは arrayLength 個の空のスロットを持つ配列であり、実際に undefined の値が入ったスロットではありません

とのこと。Array.fromを使ってようやく使える:

Array.from(Array(N)).map((_, i) => i * i)

mapに与えた関数の第一引数に値が渡されるがundefinedなので無視して、第二引数に渡されるインデクスを利用する。

array.from:Array.fromに関数を渡す

Array.fromに関数を渡せる:

Array.from(Array(N), (_, i) => i * i)

array.from.length:Array.fromにlengthを与える

Array.fromにはlengthを指定して生成する方法もあるとのこと:

Array.from({length: N}, (_, i) => i * i)

array.fill.map:Array.fillで実体化する

Array.fillで空のスロットを実体化する:

Array(N).fill().map((_, i) => i * i)

spread.map:スプレッド構文

array.from.mapと同じだが、スプレッド構文 [...] を使う:

[...Array(N)].map((_, i) => i * i)

spread.keys

インデクスからmapで加工するのであれば上のspread.mapでいいんだけど、 一応Array.prototype.keys() にスプレッドの適用も試してみる:

[...Array(N).keys()]

これだけはmapせずに、整数列を得るだけ。

spread.generator:ジェネレータを使用する

余計な呼び出しコストが必要で明らかに色々ペナルティ多そうなので言わずもがなだが、一応ジェネレータを使った場合も調べてみる:

function *range_gen(n) {
for (let i = 0; i < n; ++i)
yield i
}

[...range_gen(N)].map(i => i * i)

ジェネレータはfunction*としてしか書けず、アロー・無名関数では書けないとのこと。

実際に走らせて負荷を確認

配列サイズ: 計測回数:

考察

  • 配列に順にpushするより、あらかじめサイズを指定して確保してインデクスで格納したほうがよい
  • 配列のサイズが大きくなると差が開く
  • 環境によっても結果が変わってくるので、書きやすい方法を使うのが一番という身も蓋もない結果に…
  • にしても、array.from.mapよりもarray.fromやarray.from.lengthが遅いのが腑に落ちない…