フロントエンドJavaScriptでランダムアルゴリズムを実装する方法

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4105471/d80e6895-0231-43d9-8088-8c0d3e6b87b1.png)


この記事では、フロントエンドJavaScriptでのさまざまなランダムアルゴリズムの実装方法について探求し、異なる技術、それらの利点、および特定のユースケースに焦点を当てています。

著者はまず、フロントエンド開発におけるランダム性の重要性を説明し、ゲーム開発やデータサンプリングなどへの応用を強調しています。そして、JavaScriptでランダム値を生成するための2つの主要な方法について詳しく解説しています：

`Math.random()`メソッドは、最も単純なアプローチとして紹介されています。このメソッドは0（含む）から1（含まない）の間の浮動小数点数を生成します。著者はこの方法の限界、特にそのアルゴリズム設計のため、暗号的目的や高い予測不可能性を必要とするアプリケーションには適していないことを指摘しています。

一方、`crypto.getRandomValues()`メソッドは、より高いセキュリティを要求するアプリケーションにおいて優れた選択肢として紹介されています。著者はこのメソッドが暗号学的に安全なランダム値を生成し、トークンやキーの生成に理想的であると説明しています。強調される利点には、暗号的セキュリティ、値の均一な分布、そしてWeb Crypto APIを通じた幅広いブラウザサポートが含まれています。

記事はその後、より高度な概念へと移行し、重み付き確率を扱う際に重要なプロセスとして正規化を紹介しています。著者は、オブジェクトの配列内の重みを正規化する実践的な例を提供しています。この方法では、総重量を計算し、各個別の重みをこの合計で割ることで、合計が1になる確率を導き出します。

この基礎の上に構築して、記事は正規化された重みを使用した重み付きランダム選択の実装方法を示しています。この実装には、累積重みを計算し、それらをランダム値と比較して、それぞれの確率に基づいて選択を行うことが含まれています。

より洗練されたアプリケーションのために、著者はFisher-Yatesシャッフルアルゴリズムを配列をランダムにシャッフルする効率的な方法として紹介しています。標準的な実装が最初に示され、その後、パスワード文字セットやゲームカードなどの機密データを扱うアプリケーション向けに、crypto APIを利用した安全なバージョンが続きます。

記事全体を通して、著者は各概念に対する実践的なコード例を提供し、開発者がこれらのランダムアルゴリズムをフロントエンドJavaScriptアプリケーションに実装しやすくしています。基本的なランダム生成から、重み付き選択や安全なシャッフルなどの高度な技術への進行は、JavaScriptにおけるランダム性実装の包括的な概要を提供しています。

## Math.random()メソッド

JavaScriptで最も単純にランダム数を生成する方法は`Math.random()`メソッドを使用することです。このメソッドは0から1の間（0を含み、1を含まない）の浮動小数点数を返します。

```javascript
const randomValue = Math.random();
console.log(randomValue); // 0と1の間のランダムな数を出力
```

しかし、`Math.random()`はそのアルゴリズムがセキュアに設計されていないため、暗号的な目的や高い予測不可能性を必要とするアプリケーションには適していません。

## crypto.getRandomValues()メソッド

より高度なセキュリティを必要とするアプリケーションでは、`crypto.getRandomValues()`メソッドが最適な選択肢です。このメソッドは暗号学的に安全なランダム値を生成し、トークンやキーの生成などのタスクに理想的です。

```javascript
const array = new Uint32Array(10);
window.crypto.getRandomValues(array);
console.log(array); // 暗号学的に安全な10個のランダム整数の配列を出力
```

### crypto.getRandomValues()の利点

- **暗号的セキュリティ**: `Math.random()`とは異なり、`crypto.getRandomValues()`は暗号的使用に適した安全なランダム値を提供するよう設計されています。
- **均一な分布**: 予測可能性を減らす、より均一な値の分布を保証します。
- **ブラウザサポート**: ほとんどの現代的なブラウザはWeb Crypto APIをサポートしており、広く使用可能です。

## 正規化の理解

正規化は、異なるスケールで測定された値を共通のスケールに調整するプロセスです。ランダム選択の文脈では、重み付き確率を扱う際に正規化が重要です。

### 正規化のユースケース

例えば、関連する重みを持つオブジェクトの配列があるとします：

```javascript
const items = [
  { name: "A", weight: 1 },
  { name: "B", weight: 3 },
  { name: "C", weight: 2 },
];
```

これらの重みを正規化するには、総重量を計算し、各重みをこの合計で割ります：

```javascript
const totalWeight = items.reduce((sum, item) => sum + item.weight, 0);
const normalizedWeights = items.map(item => item.weight / totalWeight);
```

これにより、合計が1になる確率のセットが得られ、重み付きランダム選択を実装しやすくなります。

## 重み付きランダム選択の実装

正規化された重みがあれば、重みに基づいてアイテムをランダムに選択する関数を実装できます：

```javascript
function weightedRandomSelection(items) {
  const totalWeight = items.reduce((sum, item) => sum + item.weight, 0);
  const randomValue = Math.random() * totalWeight;

  let cumulativeWeight = 0;
  for (const item of items) {
    cumulativeWeight += item.weight;
    if (randomValue < cumulativeWeight) {
      return item.name; // 選択されたアイテムを返す
    }
  }
}

// 使用例
const selectedItem = weightedRandomSelection(items);
console.log(selectedItem); // 重みに基づいて "A"、"B"、または "C" を出力
```

## 高度な技術：Fisher-Yatesシャッフルアルゴリズム

Fisher-Yatesシャッフルアルゴリズムは、配列をランダムにシャッフルする効率的な方法です。このアルゴリズムは、配列のすべての順列が等しく可能性があることを保証します。

実装方法は次のとおりです：

```javascript
function fisherYatesShuffle(array) {
  for (let i = array.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [array[i], array[j]] = [array[j], array[i]]; // 要素の交換
  }
  return array;
}

// 使用例
const shuffledArray = fisherYatesShuffle([1, 2, 3, 4, 5]);
console.log(shuffledArray); // ランダムにシャッフルされた配列を出力
```

### セキュアバージョン（cryptoを使用）

パスワード文字セットやゲームカードなどの機密データをシャッフルする場合は、セキュアバージョンを使用します。

```javascript
function secureRandomInt(max) {
  const array = new Uint32Array(1);
  window.crypto.getRandomValues(array);
  return array[0] % (max + 1);
}

function secureFisherYatesShuffle(array) {
  const result = [...array];
  for (let i = result.length - 1; i > 0; i--) {
    const j = secureRandomInt(i);
    [result[i], result[j]] = [result[j], result[i]]; // 要素の交換
  }
  return result;
}
```

## 実例：重み付きランダムスピナー

重み付きランダム選択の優れた実例として、以下のインタラクティブなスピナーがあります。

👉 [Ruleta Aleatoria](https://ruletaa.net/)

このサイトでは、各セグメントに異なる重みを設定できるカスタムルーレットを作成でき、スピンした際の選択確率に影響を与えます。

ユースケース：

- 重み付けされた確率によるランダム抽選
- 教室での参加ツール
- バイアスのある意思決定アプリ
- ゲーミフィケーションされた報酬システム

このようなツールの裏側では、以前説明した重み付き選択ロジックがよく使われています。各オプションに重みを割り当て、確率を正規化し、その重みに基づいて選択を行います。

## さらに詳しく

本番環境での検証を経て、複数のプロジェクトで素早く再利用できるように、npm パッケージ [randomize-any](https://www.npmjs.com/package/randomize-any) を作成しました。  
このパッケージはさまざまな利用シーンを十分に考慮しており、サーバーサイドでもクライアントサイドでも使用できます。CJS、ESM、UMD いずれの形式にも対応しており、TypeScript の型定義にも対応しているため、開発者の使い勝手にも配慮しています。  
使い方は以下の通りです。  
その他の同型（アイソモーフィック）ライブラリについては、[isomorphism-libs](https://github.com/horushe93/isomorphism-libs) をご覧ください。

## 結論

この記事では、フロントエンド JavaScript でランダムアルゴリズムを実装するさまざまな方法を紹介しました。単純なタスクには `Math.random()` を、セキュアな用途には `crypto.getRandomValues()` を使う方法、さらに正規化や重み付きランダム選択、Fisher-Yates シャッフルや複数サンプルの重み付き選択といった高度なテクニックまで解説しました。

これらのコンセプトを理解することで、アプリケーションにおいて効果的にランダム性を実装でき、公平性や必要に応じたセキュリティも確保できるようになります。コーディングを楽しんでください！