動かざることバグの如し

近づきたいよ 君の理想に

TypeScriptで配列から指定数ランダム抽出するメソッド

やりたいこと

例えば以下のような配列があったとする。

const fruits = [
  "りんご",
  "バナナ",
  "オレンジ",
  "いちご",
  "ぶどう",
  "メロン",
  "パイナップル",
  "マンゴー",
  "スイカ",
  "さくらんぼ"
];

この中から重複せずにランダムに3つ取得したい。

コード

function getRandomElements<T>(arr: T[], count: number): T[] {
  if (count > arr.length) {
    throw new Error('Count is greater than the array length');
  }

  const result: T[] = [];
  const usedIndices: Set<number> = new Set();

  while (result.length < count) {
    const randomIndex = Math.floor(Math.random() * arr.length);
    if (!usedIndices.has(randomIndex)) {
      usedIndices.add(randomIndex);
      result.push(arr[randomIndex]);
    }
  }

  return result;
}

実行すると

getRandomElements(fruits, 3)
>  ['さくらんぼ', 'ぶどう', 'マンゴー']
getRandomElements(fruits, 2)
> ['りんご', 'パイナップル']

のようにできる。

コードの解説

  • getRandomElements<T>(arr: T[], count: number): T[] 関数の定義:
    • ジェネリック<T> を使って、任意の型の配列を受け取れるようにする。
  • 引数のチェック:
    • if (count > arr.length) で、要求した要素数が配列の長さを超えないことを確認。超えた場合はエラーをスロー。
  • 結果を格納するための配列 result と、使用したインデックスを管理するための Set 型の usedIndices を定義。
  • ランダムな要素を取得するループ:
    • while (result.length < count) で、必要な数のランダムな要素が取得できるまでループ。
    • 各ループ内で、Math.random() を使ってランダムなインデックスを生成。
    • そのインデックスが usedIndices に存在しない場合のみ、結果に追加する。
  • 最終的に、重複のないランダム要素が含まれた配列を返す。

注意点

  • 重複を避けるために、配列の長さより多くの要素を要求しないこと。
  • Set を使用しているため、インデックスの管理が効率的。
  • 配列の長さが少ない場合、要求する要素数を注意深く設定する必要がある(特に小さな配列の場合)。
  • 整数の除外やオフセットの設定はしていないため、配列の先頭から詳細な参照が必要な場合は注意が必要。