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

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 Lazy random = Lazy.of(SplittableRandom::new); 

След това ще приложим функцията за управление на вероятността:

public  withProbability(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. Други разпределения

Равномерното разпределение работи добре за моделиране на неща като игри. За да бъде играта честна, всички събития често трябва да имат еднаква вероятност да се случат.

В реалния живот обаче разпределението обикновено е по-сложно. Шансовете не са равни да се случат различни неща.

For example, there are very few extremely short people and very few extremely tall. Most people are of average height, which means that the height of people follows the normal distribution. If we need to generate random human heights, then it won't suffice to generate a random number of feet.

Fortunately, we don't need to implement the underlying mathematical model ourselves. We need to know which distribution to use and how to configure it, for example, using statistical data.

The Apache Commons library provides us with implementations for several distributions. Let's implement the normal distribution with it:

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); 

Using this API is very straightforward – the sample method draws a random number from the distribution:

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

Finally, let's invert the process:

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

В резултат ще получим вероятността човек да има височина между две граници. В този случай долната и горната височина.

5. Заключение

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

Пълният пример може да бъде намерен в GitHub.