JavaScriptでrandom7()からrandom10()関数を派生させる方法
問題
まず、次のようなアロー関数が与えられているものとします。
const random7 = () => Math.ceil(Math.random() * 7);
この関数は、呼び出すたびに1以上7以下(0を除く)のランダムな整数を返します。
ここで求められているのは、これとよく似た random10() 関数です。条件は以下の通りです。
- 引数を取らないこと
- Math.random() など乱数系APIを直接使わないこと
- サードパーティ製ライブラリにも頼らないこと
random7()関数のみを利用して、1以上10以下のランダムな整数を返すこと
シンプルな実装例:乱数の合計から剰余を取る
最も直感的なアプローチは、random7() を複数回呼び出してその合計を求め、10で割った余りを利用する方法です。
const random7 = () => Math.ceil(Math.random() * 7);
const random10 = () => {
let sum = 0; // 初期化しないとNaNになるため注意
for (let i = 0; i < 50; i++) {
sum += random7();
}
return (sum % 10) + 1;
};
console.log(random10());コードの解説
このコードでは、random7() を50回(回数は他の値でも構いません)呼び出してその合計を算出し、その合計値をもとに10進法ベースの乱数を生成しています。最後に + 1 を加えているのは、結果を1〜10の範囲に収めるためです。
よくある落とし穴:変数の初期化忘れ
元のコードのように let sum; と初期値なしで宣言すると、undefined に対して数値を加算することになり、戻り値は常に NaN(Not a Number)になってしまいます。累積用の変数は必ず let sum = 0; のように初期化するようにしましょう。
より厳密なアプローチ:棄却サンプリング
合計から剰余を取る方式は手軽ですが、数学的には完全な一様分布にはなりません。中心極限定理により、多数の乱数を足し合わせた値は正規分布に近づくため、剰余を取っても数字ごとの出現確率に偏りが生じます。
完全に一様な分布が必要な場合は、棄却サンプリング(rejection sampling)を使うのが定番です。random7() を2回呼び出して組み合わせると、7 × 7 = 49通りの等確率な状態が得られます。このうち40通りだけを採用し、残りを捨てて再抽選することで、理想的な乱数が作れます。
const random10 = () => {
let num;
do {
// (1〜7)と(1〜7)の組合せ → 1〜49の一様な整数
num = (random7() - 1) * 7 + random7();
} while (num > 40); // 41〜49は捨ててやり直す
return ((num - 1) % 10) + 1;
};
console.log(random10());この実装では、採用される1〜40の各数値がちょうど4回ずつ現れるため、% 10 の結果も完全に均等になります。ループの再抽選は平均してわずか数回程度で収まるため、パフォーマンス上の心配もほぼありません。
まとめ
動けばよいカジュアルな用途であれば「合計の剰余」方式でも十分ですが、統計的な公平性が求められる場面では、棄却サンプリングによる実装を選ぶのが賢明です。また、累積変数の初期化忘れによる NaN バグは初心者が陥りやすいポイントなので、コードレビューの際には特に注意しておきましょう。
-
JavaScriptでのメソッド借用:call()・apply()・bind()の使い方を解説
JavaScriptでは、call()、apply()、bind()という3つのメソッドを使うことで、あるオブジェクトが持つメソッドを別のオブジェクトが「借用」できます。これは、本来そのメソッドを持たないオブジェクトに対して、thisの参照先を差し替えてメソッドを実行できる便利なテクニックです。 3つのメソッドの違い call():引数をカンマ区切りで個別に渡し、即座に関数を実行します。 apply():引数を配列(または配列風オブジェクト)として渡し、即座に関数を実行します。 bind():thisを固定した新しい関数を返すだけで、即座には実行されません。返された関数は後から好きなタイミン
-
【JavaScript】外部関数の内側からイベントリスナーを削除する方法
JavaScriptで、外部関数の内側からイベントリスナーを削除したい場合は、removeEventListener()メソッドを使用します。 リスナーを削除する際のポイント removeEventListener()でリスナーを正しく削除するには、addEventListener()で登録したときと同じ関数への参照を渡す必要があります。そのため、無名関数(匿名関数)をそのまま登録してしまうと、後から削除できなくなる点に注意しましょう。本記事のサンプルコードでは、登録時に名前付き関数funを渡し、それをそのまま外部関数へ引き渡すことで、内側からの削除を実現しています。 コード例 以下が実際