Автор оригинала: Michał Dąbrowski.
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 как Ленивый используя Vavr . Таким образом, мы создадим его только один раз, по первому запросу:
private final Lazyrandom = Lazy.of(SplittableRandom::new);
Затем мы реализуем функцию управления вероятностью:
publicwithProbability(Supplier positiveCase, Supplier 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 очень просто – метод sample извлекает случайное число из распределения:
public static double generateNormalHeight() { return distribution.sample(); }
Наконец, давайте перевернем процесс:
public static double probabilityOfHeightBetween(double heightLowerExclusive, double heightUpperInclusive) { return distribution.probability(heightLowerExclusive, heightUpperInclusive); }
В результате мы получим вероятность того, что человек имеет рост между двумя границами. В этом случае нижняя и верхняя высоты.
5. Заключение
В этой статье мы узнали, как генерировать случайные события и как вычислять вероятность их возникновения. Мы использовали равномерное и нормальное распределения для моделирования различных ситуаций.
Полный пример можно найти на GitHub .