Перейти к основному содержимому

Вероятность в Java

· 4 мин. чтения

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. Вызов функции с заданной вероятностью

Теперь предположим, что мы хотим время от времени выполнять задачу и контролировать ее вероятность. Например, у нас есть сайт электронной коммерции, и мы хотим предоставить скидку 10% нашим пользователям.

Для этого давайте реализуем метод, который будет принимать три параметра: поставщика для вызова в некотором проценте случаев, второго поставщика для вызова в остальных случаях и вероятности.

Во- первых, мы объявляем наш SplittableRandom как Lazy , используя Vavr . Таким образом, мы создадим его только один раз, при первом запросе:

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. Вероятность выборки методом Монте-Карло

Давайте обратим процесс, который мы видели в предыдущем разделе. Для этого мы измерим вероятность, используя метод Монте-Карло . Он генерирует большое количество случайных событий и подсчитывает, сколько из них удовлетворяет заданному условию. Это полезно, когда вероятность трудно или невозможно вычислить аналитически.

Например, если мы посмотрим на шестигранную игральную кость, мы знаем, что вероятность выпадения определенного числа равна 1/6. Но если у нас есть таинственная игральная кость с неизвестным количеством граней, трудно сказать, какова будет вероятность. Вместо того, чтобы анализировать кости, мы могли бы просто бросать их несколько раз и подсчитывать, сколько раз происходят определенные события.

Давайте посмотрим, как мы можем реализовать этот подход. Сначала попробуем сгенерировать число 1 с вероятностью 10% миллион раз и подсчитать их:

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 очень просто — метод выборки извлекает случайное число из распределения:

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

Наконец, давайте инвертируем процесс:

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

В результате мы получим вероятность того, что человек имеет рост между двумя границами. В данном случае нижняя и верхняя высоты.

5. Вывод

В этой статье мы узнали, как генерировать случайные события и как вычислять вероятность их возникновения. Мы использовали равномерное и нормальное распределения для моделирования различных ситуаций.

Полный пример можно найти на GitHub .