1. 概要

このチュートリアルでは、Javaを使用して確率を実装する方法の例をいくつか見ていきます。

2. 基本確率のシミュレーション

Javaで確率をシミュレートするには、最初にランダムな数値を生成する必要があります。幸い、Javaにはランダムな数値ジェネレーターがたくさん用意されています。

この場合、 SplittableRandom クラスを使用します。これは、高品質のランダム性を提供し、比較的高速であるためです。

SplittableRandom random = new SplittableRandom();

次に、ある範囲で数値を生成し、その範囲から選択した別の数値と比較する必要があります。 範囲内のすべての数字は、同じ確率で引き出されます。 範囲がわかっているので、選択した数値を引く確率がわかります。 そのようにして確率を制御しています

boolean probablyFalse = random.nextInt(10) == 0

この例では、0から9までの数字を描きました。 したがって、0を引く確率は10%に等しくなります。 次に、乱数を取得して、選択した数値が描画された数値よりも小さいかどうかをテストします。

boolean whoKnows = random.nextInt(1, 101) <= 50

ここでは、1から100までの数字を描きました。 ランダムな数が50以下になる可能性は正確に50%です。

3. 一様分布

この時点までに生成された値は、一様分布に分類されます。 これは、すべてのイベント、たとえばサイコロを振るなど、すべてのイベントが同じ確率で発生することを意味します。

3.1. 与えられた確率で関数を呼び出す

ここで、タスクを時々実行し、その確率を制御したいとします。 たとえば、eコマースサイトを運営しており、ユーザーの10% ofに割引を提供したいと考えています。

そのために、3つのパラメーターを受け取るメソッドを実装しましょう。ある割合のケースで呼び出すサプライヤー、残りのケースで呼び出す2番目のサプライヤー、および確率です。

まず、 Vavr を使用して、SplittableRandomLazyとして宣言します。 このようにして、最初のリクエストで1回だけインスタンス化します。

private final Lazy<SplittableRandom> random = Lazy.of(SplittableRandom::new);

次に、確率管理機能を実装します。

public <T> withProbability(Supplier<T> positiveCase, Supplier<T> negativeCase, int probability) {
    SplittableRandom random = this.random.get();
    if (random.nextInt(1, 101) <= probability) {
        return positiveCase.get();
    } else {
        return negativeCase.get();
    }
}

3.2. モンテカルロ法によるサンプリング確率

前のセクションで見たプロセスを逆にしましょう。 そのために、 MonteCarloメソッドを使用して確率を測定します。 大量のランダムイベントを生成し、提供された条件を満たすイベントの数をカウントします。 確率を分析的に計算するのが難しいか不可能な場合に便利です。

たとえば、6面のサイコロを見ると、特定の数字を振る確率は1/6であることがわかります。 しかし、辺の数が不明な不思議なサイコロがあるとしたら、その確率がどうなるかはわかりません。 サイコロを分析する代わりに、サイコロを何回も振って、特定のイベントが発生した回数を数えることができます。

このアプローチを実装する方法を見てみましょう。 まず、10% fまたは100万回の確率で1を生成し、それらをカウントしようとします。

int numberOfSamples = 1_000_000;
int probability = 10;
int howManyTimesInvoked = 
  Stream.generate(() -> randomInvoker.withProbability(() -> 1, () -> 0, probability))
    .limit(numberOfSamples)
    .mapToInt(e -> e)
    .sum();

次に、生成された数の合計をサンプル数で割ると、イベントの確率の概算になります。

int monteCarloProbability = (howManyTimesInvoked * 100) / numberOfSamples;

計算された確率は概算です。 サンプル数が多いほど、近似は良くなります。

4. その他のディストリビューション

一様分布は、ゲームなどのモデリングに適しています。 ゲームが公平であるためには、すべてのイベントが同じ確率で発生する必要があります。

ただし、実際には、配布は通常、より複雑です。 さまざまなことが起こる可能性は同じではありません。

たとえば、非常に背の低い人や背の高い人はほとんどいません。 ほとんどの人は平均的な身長です。つまり、人の身長は正規分布に従います。 ランダムな人間の身長を生成する必要がある場合は、ランダムな数の足を生成するだけでは不十分です。

幸い、基礎となる数学モデルを自分で実装する必要はありません。 たとえば、統計データを使用して、使用するディストリビューションとその構成方法を知る必要があります。

Apache Commonsライブラリは、いくつかのディストリビューションの実装を提供します。 それを使って正規分布を実装しましょう:

private static final double MEAN_HEIGHT = 176.02;
private static final double STANDARD_DEVIATION = 7.11;
private static NormalDistribution distribution =  new NormalDistribution(MEAN_HEIGHT, STANDARD_DEVIATION);

このAPIの使用は非常に簡単です。sampleメソッドは、分布から乱数を取得します。

public static double generateNormalHeight() {
    return distribution.sample();
}

最後に、プロセスを逆にしましょう。

public static double probabilityOfHeightBetween(double heightLowerExclusive, double heightUpperInclusive) {
    return distribution.probability(heightLowerExclusive, heightUpperInclusive);
}

その結果、人が2つの境界の間に高さを持っている確率が得られます。 この場合、下部と上部の高さ。

5. 結論

この記事では、ランダムなイベントを生成する方法と、それらが発生する確率を計算する方法を学びました。 さまざまな状況をモデル化するために、均一分布と正規分布を使用しました。

完全な例は、GitHubにあります。