1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

確率で条件分岐するコード

Last updated at Posted at 2021-08-11

はじめに

例えば、60%の確率でa、25%の確率でb、15%の確率でcを出力するようなプログラムを実装します。

※本稿では、実装例として言語はTypeScriptで書いていますが、本稿の主旨に言語は関係ありません。

失敗例

この問題の解法としてパッと思いつくのは、以下のようなif文で分岐させるコードです。

/// 失敗例

// [1,100]の区間でランダムな整数を返す関数
const rand = () => Math.floor(Math.random() * 100) + 1

// 小数点第一位で丸める関数
const round = v => Math.round(v * 10) / 10

// 試行回数
const N = 10000

let a = 0, b = 0, c = 0
for (let i = 0; i < N; i++) {
    if (rand() <= 60) {
        // 60%の確率でa
        a++
    } else if (rand() <= 25) {
        // 25%の確率でb
        b++
    } else {
        // 15%の確率でc
        c++
    }
}

// 結果出力
console.log(`a: ${round(a / N * 100)}%\nb: ${round(b / N * 100)}%\nc: ${round(c / N * 100)}%`)

このプログラムは一見正解な気がしますが、実際の結果は

a: 59.7%
b: 9.7%
c: 30.6%

みたいになります。aは60%に近い結果となっていますが、bとcはかなり違っています。これは何故でしょうか?

失敗例のコードだと、bのブロックに入る実際の確率は、

(100% - 60%) * 25% = 10%

です。これはaではない(確率40%)かつbである(確率25%)という条件になってしまっているからです。

正解例

正解は以下のようなコードになります。

/// 正解例

// 小数点第一位で丸める関数
const round = v => Math.round(v * 10) / 10

// 試行回数
const N = 10000

let a = 0, b = 0, c = 0
for (let i = 0; i < N; i++) {
    switch (randomWithProbabilityTable({
        a: 60,
        b: 25
    })) {
        case "a":
            a++
            break
        case "b":
            b++
            break
        default:
            c++
    }
}

// 結果出力
console.log(`a: ${round(a / N * 100)}%\nb: ${round(b / N * 100)}%\nc: ${round(c / N * 100)}%`)

randomWithProbabilityTable関数の実装は以下の通りです。

function randomWithProbabilityTable<
    TABLE extends { [key: string]: number }
>(probabilityTable: TABLE): keyof TABLE | null {
    // 確率テーブルを確率の低い順にソート
    const table = Object.entries(probabilityTable)
        .sort(([, a], [, b]) => a - b)
        .reduce(
            (result, [key, val]) => ({ ...result, [key]: val }),
            {}
        ) as TABLE

    // [1,100]の区間でランダムな整数を生成
    const rand = Math.floor(Math.random() * 100) + 1
    let rate = 0
    for (const key in table) {
        rate += table[key]

        if (rand <= rate) {
            return key
        }
    }

    return null
}

このプログラムの実行結果は以下のようになります。

a: 60.3%
b: 24.8%
c: 14.9%

だいたい期待した通りの数値に近い結果となっています。

確率テーブルに則った結果を得たいときは、正解例のコードのようにひと手間必要です。

randomWithProbabilityTableのような関数を1つ用意しておけば、引数を変えるだけで色々な場面で使い回せます。

参考

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?