🌊

JavaScript で Python の range みたいなやつ

2024/08/26に公開

主題

JavaScript には Python の range みたいなやつが標準で存在しない。Python で以下のように書けるコードは

Python
print(list(map(lambda x: x * 2, range(5))))
# [0, 2, 4, 6, 8]

JavaScript で愚直に書くと、以下のようになってしまう。

JavaScript
const start = 0;
const stop = 5;
const step = 1;
const length = Math.floor((stop - start) / step);
const a = new Array(length);
for (let i = 0; i < length; i++) {
    a[i] = start + step * i;
}
console.log(a.map(x => x * 2));
// [0, 2, 4, 6, 8]

私が思うに、このコードには 2 つの問題点がある。

  1. 空の配列を作って、for 文で値を計算して代入して、と長いコードを書いて反復処理可能なオブジェクトを作成しなければならない。
  2. Python の range に相当する配列を map する前に作成してしまっている。

「JavaSciprt で Python の range みたいなものを使うにはどうすれば良いか」を Google で検索すると、

[...Array(5).keys()]

などとする方法や

Array.from(Array(5).keys)

などとする方法が出てくるが、最終成果物に関係のない Array(5) を作成する必要があるし、任意の start, stop, step に使用することができず、2の問題点も解決できていないので、邪道に思える。

個人的なおすすめとしては、生成関数 (generator function) で自作する方法である。生成関数が返すオブジェクトは Generator だ。継承される IteratorIterator.prototype.forEachIterator.prototype.mapIterator.prototype.toArray は Chrome とかでしかまだ使えないので、Array.from を使って Array に変換する必要が多いだろう。その意味では2の問題点は解消できていないかもしれない。そのうち Array.from を使わなくても良くなると思う。MDN Web Docs の Iteration protocols を見ると良い。

Python の range と大体同じなやつ
function * range(start, stop, step = 1) {
    if (start === undefined) {
        throw new Error("Need at least one argument.");
    }
    if (stop === undefined) {
        stop = start;
        start = 0;
    }
    if (step > 0) {
        for (let n = start; n < stop; n += step) {
            yield n;
        }
    } else if (step < 0) {
        for (let n = start; n > stop; n += step) {
            yield n;
        }
    } else {
        throw new Error("step cannot be zero.");
    }
}

console.log(Array.from(range(5))); // [ 0, 1, 2, 3, 4 ]
console.log(Array.from(range(-2, 2))); // [ -2, -1, 0, 1 ]
console.log(Array.from(range(0, 10, 2))); // [ 0, 2, 4, 6, 8 ]
console.log(Array.from(range(10, 0, -2))); // [ 10, 8, 6, 4, 2 ]

おまけ

私は Julia に慣れてしまっているので、Python の range よりも Julia の StepRange の方が使いやすい。range(n)1:n ということにして実装すると

Julia の StepRange と大体同じなやつ
function * range(start, step, stop) {
    if (start === undefined && step === undefined && stop === undefined) {
        throw new Error("Need at at least one argument.");
    }
    if (step === undefined && stop === undefined) {
        stop = start;
        start = 1;
        step = 1;
    }
    if (stop === undefined) {
        stop = step;
        step = 1;
    }
    if (step > 0) {
        for (let n = start; n <= stop; n += step) {
            yield n;
        }
    } else if (step < 0) {
        for (let n = start; n >= stop; n += step) {
            yield n;
        }
    } else {
        throw new Error("step cannot be zero.");
    }
}

となる。

Discussion