Probabilità in Java

1. Panoramica

In questo tutorial, esamineremo alcuni esempi di come possiamo implementare la probabilità con Java.

2. Simulazione della probabilità di base

Per simulare la probabilità in Java, la prima cosa che dobbiamo fare è generare numeri casuali. Fortunatamente, Java ci fornisce molti generatori di numeri casuali .

In questo caso, useremo la classe SplittableRandom perché fornisce casualità di alta qualità ed è relativamente veloce:

SplittableRandom random = new SplittableRandom();

Quindi dobbiamo generare un numero in un intervallo e confrontarlo con un altro numero scelto da quell'intervallo. Ogni numero nell'intervallo ha la stessa possibilità di essere estratto. Poiché conosciamo l'intervallo, conosciamo la probabilità di estrarre il nostro numero scelto. In questo modo controlliamo la probabilità :

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

In questo esempio, abbiamo disegnato i numeri da 0 a 9. Pertanto, la probabilità di estrarre 0 è uguale al 10%. Ora, otteniamo un numero casuale e testiamo se il numero scelto è inferiore a quello estratto:

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

Qui, abbiamo disegnato numeri da 1 a 100. La possibilità che il nostro numero casuale sia minore o uguale a 50 è esattamente del 50%.

3. Distribuzione uniforme

I valori generati fino a questo punto rientrano nella distribuzione uniforme. Ciò significa che ogni evento, ad esempio il lancio di un numero su un dado, ha le stesse possibilità di accadere.

3.1. Invocare una funzione con una determinata probabilità

Supponiamo ora di voler eseguire un'attività di tanto in tanto e controllarne la probabilità. Ad esempio, gestiamo un sito di e-commerce e vogliamo dare uno sconto al 10% dei nostri utenti.

Per fare ciò, implementiamo un metodo che richieda tre parametri: un fornitore da invocare in una certa percentuale di casi, un secondo fornitore da invocare nel resto dei casi e la probabilità.

Innanzitutto, dichiariamo il nostro SplittableRandom come Lazy usando Vavr. In questo modo lo istanzeremo solo una volta, alla prima richiesta:

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

Quindi, implementeremo la funzione di gestione delle probabilità:

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. Probabilità di campionamento con il metodo Monte Carlo

Invertiamo il processo che abbiamo visto nella sezione precedente. Per fare ciò, misureremo la probabilità utilizzando il metodo Monte Carlo. Genera un volume elevato di eventi casuali e conta quanti di essi soddisfano la condizione fornita. È utile quando la probabilità è difficile o impossibile da calcolare analiticamente.

Ad esempio, se guardiamo i dadi a sei facce sappiamo che la probabilità di ottenere un certo numero è 1/6. Ma se abbiamo un dado misterioso con un numero imprecisato di lati, sarebbe difficile dire quale sarebbe la probabilità. Invece di analizzare i dadi, potremmo semplicemente lanciarli numerose volte e contare quante volte si verificano determinati eventi.

Vediamo come possiamo implementare questo approccio. Innanzitutto, proveremo a generare il numero 1 con la probabilità del 10% per un milione di volte e li conteremo:

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

Quindi, la somma dei numeri generati divisa per il numero di campioni sarà un'approssimazione della probabilità dell'evento:

int monteCarloProbability = (howManyTimesInvoked * 100) / numberOfSamples; 

Tieni presente che la probabilità calcolata è approssimata. Maggiore è il numero di campioni, migliore sarà l'approssimazione.

4. Altre distribuzioni

La distribuzione uniforme funziona bene per modellare cose come i giochi. Perché il gioco sia corretto, tutti gli eventi spesso devono avere la stessa probabilità di accadere.

Tuttavia, nella vita reale, le distribuzioni sono generalmente più complicate. Le possibilità non sono uguali che accadano cose diverse.

Ad esempio, ci sono pochissime persone estremamente basse e pochissime persone estremamente alte. La maggior parte delle persone ha un'altezza media, il che significa che l'altezza delle persone segue la distribuzione normale. Se abbiamo bisogno di generare altezze umane casuali, non sarà sufficiente generare un numero casuale di piedi.

Fortunatamente, non abbiamo bisogno di implementare noi stessi il modello matematico sottostante. Dobbiamo sapere quale distribuzione utilizzare e come configurarla , ad esempio, utilizzando dati statistici.

La libreria Apache Commons ci fornisce le implementazioni per diverse distribuzioni. Con esso implementiamo la distribuzione normale:

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

L'utilizzo di questa API è molto semplice: il metodo di esempio estrae un numero casuale dalla distribuzione:

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

Infine, invertiamo il processo:

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

Di conseguenza, otterremo la probabilità che una persona abbia un'altezza compresa tra due limiti. In questo caso, l'altezza inferiore e quella superiore.

5. conclusione

In questo articolo abbiamo imparato come generare eventi casuali e come calcolare la probabilità che si verifichino. Abbiamo utilizzato distribuzioni uniformi e normali per modellare situazioni diverse.

L'esempio completo può essere trovato su GitHub.