JavaScript Tips | 配列ユーティリティ:ランダム要素取得

JavaScript JavaScript
スポンサーリンク

何をしたいユーティリティか:「ランダム要素取得」

ここでの「ランダム要素取得」は、配列の中から 1 要素(または複数要素)をランダムに取り出す処理です。
「配列シャッフル」が「順番を全部ランダムにする」のに対して、「ランダム要素取得」は「欲しい数だけランダムに抜き取る」イメージです。

業務だと、例えば次のような場面で使います。

画面に表示するおすすめ商品を、候補リストからランダムに 1 件選ぶ。
テストデータの中から、毎回違うサンプルを 1 件だけ使いたい。
ユーザーをランダムに 1 人ピックアップして、デモ用に使いたい。

こういう処理を毎回手書きするのではなく、「ランダム要素取得ユーティリティ」としてまとめておくと、コードがかなりスッキリします。


一番基本:配列からランダムに 1 要素を取り出す

単純な 1 要素取得の実装

まずは、「配列から 1 要素だけランダムに取り出す」関数です。

function sampleOne(array) {
  if (!Array.isArray(array) || array.length === 0) {
    return undefined;
  }

  const index = Math.floor(Math.random() * array.length);

  return array[index];
}
JavaScript

重要なポイントをかみ砕いて説明する

最初に「配列かどうか」と「空配列でないか」をチェックしています。

if (!Array.isArray(array) || array.length === 0) {
  return undefined;
}
JavaScript

空配列からは要素を取り出せないので、その場合は undefined を返すようにしています。
業務コードでは、「空のときどうするか」を決めておくことが大事です。

次に、ランダムなインデックスを計算しています。

const index = Math.floor(Math.random() * array.length);
JavaScript

Math.random() は 0 以上 1 未満のランダムな数を返します。
それに array.length を掛けると、0 以上 length 未満の実数になります。
Math.floor で小数点以下を切り捨てることで、0 以上 length - 1 以下の整数がランダムに 1 つ得られます。
この範囲が「配列の有効なインデックスの範囲」と一致しているのがポイントです。

最後に、そのインデックスの要素を返します。

return array[index];
JavaScript

これで、「配列の中から 1 要素をランダムに取り出す」処理が完成です。

実際の動き

const colors = ["red", "green", "blue", "yellow"];

sampleOne(colors); // 例: "green"
sampleOne(colors); // 例: "blue"
sampleOne(colors); // 例: "red"
JavaScript

何度呼び出しても、毎回どれが返ってくるかは分かりませんが、
どの要素も「同じ確率」で選ばれます。


複数要素をランダムに取り出す(重複なし)

「ランダムに N 件だけ欲しい」ケース

業務では、「1 件だけ」ではなく「ランダムに 3 件欲しい」「ランダムに 10 件欲しい」といったケースもよくあります。
しかも、「同じ要素を 2 回選びたくない(重複なし)」ことが多いです。

この場合は、「シャッフル+先頭 N 件」が一番素直です。

function sampleMany(array, count) {
  if (!Array.isArray(array) || array.length === 0 || count <= 0) {
    return [];
  }

  const copy = array.slice();

  for (let i = copy.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    const tmp = copy[i];
    copy[i] = copy[j];
    copy[j] = tmp;
  }

  return copy.slice(0, Math.min(count, copy.length));
}
JavaScript

重要なポイントを深掘りする

最初に、入力チェックをしています。

if (!Array.isArray(array) || array.length === 0 || count <= 0) {
  return [];
}
JavaScript

配列でない、空配列、count が 0 以下のときは、結果は空配列にしています。
「何も選べない状況では空配列」というルールにしておくと、呼び出し側の処理がシンプルになります。

次に、元の配列を壊さないようにコピーを作っています。

const copy = array.slice();
JavaScript

ここから先は、「配列シャッフル」で説明した Fisher–Yates シャッフルと同じです。
copy をインプレースでシャッフルし、最後に先頭 count 件を切り出します。

return copy.slice(0, Math.min(count, copy.length));
JavaScript

count が配列の長さより大きい場合でも、Math.min で長さに丸めているので、
「存在しない数を要求してエラーになる」ことはありません。

実際の動き

const users = ["A", "B", "C", "D", "E"];

sampleMany(users, 2); // 例: ["C", "A"]
sampleMany(users, 3); // 例: ["E", "B", "D"]
sampleMany(users, 10); // 例: ["B", "E", "A", "D", "C"](最大でも 5 件)
JavaScript

毎回違う組み合わせが返ってきますが、
「同じ要素が 2 回入る」ことはありません。


複数要素をランダムに取り出す(重複あり)

「重複してもいいから N 回ランダムに選びたい」ケース

ときどき、「同じ要素が何度選ばれてもよい」ケースもあります。
例えば、「ガチャを 10 回引く」ようなイメージです。

この場合は、単純に sampleOnecount 回呼び出すだけで実現できます。

function sampleManyWithReplacement(array, count) {
  if (!Array.isArray(array) || array.length === 0 || count <= 0) {
    return [];
  }

  const result = [];

  for (let i = 0; i < count; i++) {
    result.push(sampleOne(array));
  }

  return result;
}
JavaScript

実際の動き

const prizes = ["A", "B", "C"];

sampleManyWithReplacement(prizes, 5);
// 例: ["B", "C", "B", "A", "B"]  // 同じものが何度も出てよい
JavaScript

「重複なし」と「重複あり」で関数を分けておくと、
呼び出し側の意図がコードにそのまま表れて読みやすくなります。


実務で意識してほしい設計のポイント

「空配列のときどうするか」を必ず決める

ランダム要素取得で一番ハマりやすいのは、「空配列から選ぼうとしてエラーになる」パターンです。
例えば、array.length が 0 のときに Math.random() * array.length をしても 0 になり、
index は 0 になりますが、array[0]undefined です。

ここを「仕様」としてどう扱うかを決めておくのが大事です。

1 要素取得なら「空配列なら undefined を返す」。
複数要素取得なら「空配列なら空配列を返す」。

といったルールをユーティリティ側で固定しておくと、
呼び出し側は「戻り値が undefined や空配列のときは“選べなかった”とみなす」と決めるだけで済みます。

「重複あり」と「重複なし」を明確に分ける

業務でのバグの典型パターンのひとつが、「重複してはいけないのに重複してしまう」ケースです。
ランダム要素取得でも同じで、

重複してはいけないのに sampleManyWithReplacement 的な動きをしてしまう。
重複してもいいのに、わざわざシャッフルしてから切り出している。

といったミスマッチが起きがちです。

関数名で意図をはっきりさせるのがおすすめです。

sampleMany → 重複なし。
sampleManyWithReplacement → 重複あり。

こうしておくと、コードを読むだけで「どういう前提のランダムなのか」が分かります。

「ランダム性の質」がどれくらい重要かを考える

Math.random() は「暗号学的に強い乱数」ではありませんが、
多くの業務用途(表示順のランダム化、テストデータのランダム化など)では十分です。

ただし、「抽選」「くじ」「セキュリティに関わるトークン生成」など、
不正や予測可能性が問題になる場面では、Math.random() では不十分です。

ランダム要素取得ユーティリティは、基本的には「業務ロジック用の軽いランダム」として位置づけ、
「セキュリティ用途のランダム」は別の仕組み(暗号学的乱数)で扱う、と分けて考えるのが安全です。


少し手を動かして感覚をつかむ

コンソールで、次のようなコードを何度か実行してみてください。

const colors = ["red", "green", "blue", "yellow"];

sampleOne(colors);

sampleMany(colors, 2);
sampleMany(colors, 10);

sampleManyWithReplacement(colors, 5);
JavaScript

「毎回違う結果になること」「重複なし版と重複あり版で結果の性質が違うこと」を、自分の目で確認してみてください。

そのうえで、自分のプロジェクトに

export function sampleOne(...) { ... }
export function sampleMany(...) { ... }
export function sampleManyWithReplacement(...) { ... }
JavaScript

のような関数を置き、

「配列からランダムに取りたくなったら、必ずこの“ランダム要素取得ユーティリティ”を通す」

というルールを作ってみてください。
それだけで、あなたの「ランダムな選択」は、場当たり的な書き方から、意図と一貫性を備えた業務レベルの実装に変わっていきます。

タイトルとURLをコピーしました