1. はじめに
JavaScript の Math.random() を使ったランダム選択は、すべての選択肢が等確率になりがちです。しかし、特定の要素を高確率で選ばせたい場合には 「重み付きランダム(Weighted Random)」 を使うと便利です。
本記事では、TypeScript で 重み付きランダム関数 を実装し、実際にコードを動かす方法を解説します。
2. 重み付きランダム関数 weightedRandom の実装
以下の weightedRandom 関数を実装することで、配列内のアイテムを 指定した確率(重み) に基づいてランダムに選択できます。
/**
* 指定されたアイテムの配列から、重み(確率)に基づいてランダムに1つ選ぶ関数
* @param {T[]} items - ランダムに選択したいアイテムの配列
* @param {number[]} [weights] - 各アイテムの重み(確率)を指定する配列(省略可能)
* @returns {T} - 選択されたアイテム
*/
function weightedRandom<T>(items: T[], weights?: number[]): T {
const TOTAL_PERCENTAGE = 100; // 全体の重みの基準値(必ずしも100である必要はない)
const DEFAULT_WEIGHT = 1; // デフォルトの重み(未指定の場合の重み)
const MIN_WEIGHT = 0; // 最小の重み(負の値は許容しない)
// weights が未指定または空なら、すべてのアイテムの重みを均等に設定
if (!weights || weights.length === 0) {
weights = Array(items.length).fill(DEFAULT_WEIGHT);
}
// 指定された重みの合計を計算
const totalSpecifiedWeight = weights.reduce((sum, weight) => sum + weight, MIN_WEIGHT);
// 指定されていないアイテムの数を計算
const remainingItemsCount = items.length - weights.length;
// 残りのアイテムに割り当てる重みを計算
const remainingWeight = Math.max(TOTAL_PERCENTAGE - totalSpecifiedWeight, MIN_WEIGHT);
// 残りのアイテムがある場合、それぞれに均等な重みを割り当てる
const remainingItemWeight = remainingItemsCount > 0 ? remainingWeight / remainingItemsCount : MIN_WEIGHT;
// 重みのリストを調整(指定されたもの + 自動計算されたもの)
const adjustedWeights = [
...weights,
...Array(remainingItemsCount).fill(remainingItemWeight),
];
// 調整された重みの合計を再計算
const totalWeight = adjustedWeights.reduce((sum, weight) => sum + weight, MIN_WEIGHT);
// 0 以上 totalWeight 未満のランダムな値を生成
let random = Math.random() * totalWeight;
// 各アイテムの重みを順番に引いていき、ランダム値がその範囲にあるアイテムを選択
for (let i = 0; i < items.length; i++) {
if (random < adjustedWeights[i]) {
return items[i];
}
random -= adjustedWeights[i];
}
// 万が一すべての条件を通過してしまった場合、最後のアイテムを返す
return items[items.length - 1];
}
3. 関数の動作例
以下のように weightedRandom 関数を呼び出すことで、指定した確率に基づいてランダムな値を取得できます。
4. TypeScript 環境をセットアップ
- プロジェクトのディレクトリを作成
- package.json を作成
- TypeScript の開発環境を整えるために、必要なパッケージをインストール
mkdir ts-practice
cd ts-practice
npm init -y
npm install --save-dev typescript ts-node @types/node
code . # vscodeを立ち上げる
4.TypeScript の設定ファイルを作成
npx tsc --init
5.スクリプトを追加
package.json の "scripts" セクションに、以下のスクリプトを追加します。
"scripts": {
"start": "ts-node index.ts"
},
6. TypeScript コードをターミナルで実行
touch index.ts
echo '/**
* 指定された重みに基づいて、ランダムに `items` の中から 1 つを選択する関数
*
* @param items 選択対象のアイテムの配列(例: `['A', 'B', 'C', 'D']`)
* @param weights 各アイテムの選択確率を示す数値の配列(例: `[60, 30]`)
* - `weights` を指定しない場合、すべての `items` が均等確率で選ばれる
* - `weights` の合計が 100% 未満の場合、残りの確率は未指定のアイテムに均等配分される
* @returns ランダムに選ばれた `items` の要素
*
* @example
* A(60%)、B(30%)、C, D(5%ずつ)
* weightedRandom(['A', 'B', 'C', 'D'], [60, 30]);
*
* A(40%)、B(30%)、C(20%)、D(10%)
* weightedRandom(['A', 'B', 'C', 'D'], [40, 30, 20, 10]);
*
* A, B, C, D の確率が均等 (25%ずつ)
* weightedRandom(['A', 'B', 'C', 'D']);
*/
function weightedRandom<T>(items: T[], weights?: number[]): T {
const TOTAL_PERCENTAGE = 100;
const DEFAULT_WEIGHT = 1;
const MIN_WEIGHT = 0;
if (!weights || weights.length === 0) {
// weights が未指定または空なら、全アイテムを均等にする
weights = Array(items.length).fill(DEFAULT_WEIGHT);
}
const totalSpecifiedWeight = weights.reduce(
(sum, weight) => sum + weight,
MIN_WEIGHT,
);
const remainingItemsCount = items.length - weights.length;
// 残りの割合を計算(TOTAL_PERCENTAGE から既存の合計を引く)
const remainingWeight = Math.max(
TOTAL_PERCENTAGE - totalSpecifiedWeight,
MIN_WEIGHT,
);
// 残りのアイテムがある場合、それぞれに均等に配分
const remainingItemWeight =
remainingItemsCount > 0
? remainingWeight / remainingItemsCount
: MIN_WEIGHT;
const adjustedWeights = [
...weights,
...Array(remainingItemsCount).fill(remainingItemWeight),
];
const totalWeight = adjustedWeights.reduce(
(sum, weight) => sum + weight,
MIN_WEIGHT,
);
let random = Math.random() * totalWeight;
// random が現在の adjustedWeights[i] 未満なら、そのアイテムを返す(選択される)。
// そうでない場合、random から adjustedWeights[i] を引いて次の候補へ。
for (let i = 0; i < items.length; i++) {
if (random < adjustedWeights[i]) {
return items[i];
}
random -= adjustedWeights[i];
}
return items[items.length - 1]; // 理論上ほぼ起こらないが、念のため最後の要素を返す
}
// true (70%) / false (30%)
console.log(weightedRandom([true, false], [70, 30]));
// true (90%) / false (10%)
console.log(weightedRandom([true, false], [90, 10]));
// true/false が均等確率 (50%ずつ)
console.log(weightedRandom([true, false]));
enum PostStatus {
Publish = "Publish",
Draft = "Draft",
}
// 公開 (80%) / 下書き (20%)
console.log(weightedRandom([PostStatus.Publish, PostStatus.Draft], [80, 20]));
// 均等確率で選択
console.log(weightedRandom([PostStatus.Publish, PostStatus.Draft]));
console.log(weightedRandom([10, 20, 30, 40, 50], [50, 20, 10]));
// 10(50%), 20(20%), 30(10%), 40(5%), 50(5%)
console.log(weightedRandom(["Apple", "Banana", "Cherry", "Date"]));
// 全て均等
const loot = ["Common", "Rare", "Epic", "Legendary"];
console.log(weightedRandom(loot, [60, 30, 9, 1]));
enum UserRole {
Admin = "Admin",
User = "User",
Guest = "Guest",
}
// Admin(10%), User(80%), Guest(10%)
console.log(weightedRandom([UserRole.Admin, UserRole.User, UserRole.Guest], [10, 80, 10]));
' > index.ts
実行
npm start
7. まとめ
本記事では、TypeScript で 重み付きランダム関数 を実装し、実際に環境をセットアップして動かす方法を解説しました。
今回学んだポイント
✅ 重み付きランダムとは?
✅ TypeScript での実装方法
✅ 開発環境のセットアップ
✅ 実際に動作させる方法
この weightedRandom 関数を活用すれば、確率に基づいたランダムな選択を行うことができ、シードデータの作成 などに役立てることができます。
実際に、私も NestJS でシードデータを作成する際に活用しました。
例えば、ユーザーのロール(管理者・一般ユーザーなど)や、ランダムなデータのバリエーションを調整するのに便利です。確率をコントロールしながらデータを生成できるので、テストデータをより実用的なものにすることができます。
ぜひ、シードデータの作成やその他の用途に活用してみてください! 🚀