okayurisotto.net

私が好きでやったことが他の人のためにもなったらお得かも!

TypeScriptでもPythonみたくrangeしたい!

  1. 📝
  2. 🔄

はじめに

Pythonにあるrange関数に似たものをTypeScriptで実装してみました。

Pythonのrange関数とは

引数としてstartstopstepの3つを受け取り、数列を作る関数です。

実装

export function* range({ start = 0, stop = Number.POSITIVE_INFINITY, step = 1 }) {
  if (step === 0) throw new Error();

  for (let index = start;; index += step) {
    if (stop > 0 && !(index < stop)) break;
    if (stop < 0 && !(index > stop)) break;

    yield index;
  }
}

テストコード as a 使用例

Denoを使っています。

import { range } from "./main.ts";
import { assertEquals } from "https://deno.land/[email protected]/testing/asserts.ts";

Deno.test("(10)", () => {
  assertEquals(
    [...range({ stop: 10 })],
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
  );
});

Deno.test("(1, 11)", () => {
  assertEquals(
    [...range({ start: 1, stop: 11 })],
    [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
  );
});

Deno.test("(0, 30, 5)", () => {
  assertEquals(
    [...range({ start: 0, stop: 30, step: 5 })],
    [0, 5, 10, 15, 20, 25],
  );
});

Deno.test("(0, 10, 3)", () => {
  assertEquals(
    [...range({ start: 0, stop: 10, step: 3 })],
    [0, 3, 6, 9],
  );
});

Deno.test("(0, -10, -1)", () => {
  assertEquals(
    [...range({ start: 0, stop: -10, step: -1 })],
    [0, -1, -2, -3, -4, -5, -6, -7, -8, -9],
  );
});

Deno.test("(0)", () => {
  assertEquals(
    [...range({ stop: 0 })],
    [],
  );
});

Deno.test("(1, 0)", () => {
  assertEquals(
    [...range({ start: 1, stop: 0 })],
    [],
  );
});

性能

[...range({ start: 0, stop: 1000, step: -3 })]を1回だけ実行するのにかかる時間は、手元のパソコンでは7.64μsでした。

ただしこれはイテレータからスプレッド構文ですべての要素を取り出して配列にしているので、すべての要素には用がない場面(for-ofループの途中でbreakするときなど)ではその分の計算コストを省けます。極端な話をすると、イテレータの作成だけしてスプレッド構文を使った配列化をしなかったら、1回の実行では52nsしかかかりません。

ただ、すべての要素に用がある場面では下のようなnew Array()を使ったものの方が高速です。

export const range = ({
  start = 0,
  stop = Number.POSITIVE_INFINITY,
  step = 1,
}) => {
  if (step === 0) throw new Error();

  const length = Math.ceil((stop - start) / step);
  if (length <= 0) return [];

  return new Array(length).fill(null).map((_, i) => {
    return start + i * step;
  });
};

おわりに

TypeScriptなのに型定義ねーじゃーん!(なくても推論されるため)