Xác suất trong Java

1. Khái quát chung

Trong hướng dẫn này, chúng ta sẽ xem xét một vài ví dụ về cách chúng ta có thể triển khai xác suất với Java.

2. Mô phỏng xác suất cơ bản

Để mô phỏng xác suất trong Java, điều đầu tiên chúng ta cần làm là tạo các số ngẫu nhiên. May mắn thay, Java cung cấp cho chúng ta rất nhiều trình tạo số ngẫu nhiên .

Trong trường hợp này, chúng tôi sẽ sử dụng lớp SplittableRandom vì nó cung cấp tính ngẫu nhiên chất lượng cao và tương đối nhanh:

SplittableRandom random = new SplittableRandom();

Sau đó, chúng ta cần tạo một số trong một phạm vi và so sánh nó với một số khác được chọn từ phạm vi đó. Mọi số trong phạm vi đều có cơ hội được rút ra như nhau. Khi chúng ta biết phạm vi, chúng ta biết xác suất rút ra số đã chọn của chúng ta. Bằng cách đó, chúng tôi đang kiểm soát xác suất :

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

Trong ví dụ này, chúng tôi đã vẽ các số từ 0 đến 9. Do đó, xác suất để rút ra số 0 là 10%. Bây giờ, hãy lấy một số ngẫu nhiên và kiểm tra xem số được chọn có thấp hơn số được rút ra hay không:

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

Ở đây, chúng tôi đã rút ra các số từ 1 đến 100. Cơ hội để số ngẫu nhiên của chúng tôi nhỏ hơn hoặc bằng 50 chính xác là 50%.

3. Phân phối đồng nhất

Giá trị được tạo ra cho đến thời điểm này thuộc phân phối đồng đều. Điều này có nghĩa là mọi sự kiện, ví dụ như quay một số trên xúc xắc, đều có cơ hội xảy ra như nhau.

3.1. Gọi một hàm với một xác suất cho trước

Bây giờ, giả sử chúng tôi muốn thực hiện một nhiệm vụ theo thời gian và kiểm soát xác suất của nó. Ví dụ: chúng tôi vận hành một trang thương mại điện tử và chúng tôi muốn giảm giá cho 10% người dùng của mình.

Để làm như vậy, hãy triển khai một phương pháp sẽ nhận ba tham số: nhà cung cấp để gọi trong một số trường hợp, nhà cung cấp thứ hai để gọi trong các trường hợp còn lại và xác suất.

Đầu tiên, chúng tôi khai báo SplittableRandom của chúng tôi là Lazy bằng cách sử dụng Vavr. Bằng cách này, chúng tôi sẽ khởi tạo nó chỉ một lần, theo yêu cầu đầu tiên:

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

Sau đó, chúng tôi sẽ triển khai chức năng quản lý xác suất:

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. Xác suất lấy mẫu với phương pháp Monte Carlo

Hãy đảo ngược quá trình mà chúng ta đã thấy trong phần trước. Để làm như vậy, chúng tôi sẽ đo xác suất bằng phương pháp Monte Carlo. Nó tạo ra một lượng lớn các sự kiện ngẫu nhiên và đếm xem có bao nhiêu sự kiện trong số đó thỏa mãn điều kiện đã cho. Nó hữu ích khi xác suất khó hoặc không thể tính toán phân tích.

Ví dụ, nếu chúng ta nhìn vào con xúc xắc sáu mặt, chúng ta biết rằng xác suất để lăn một số nhất định là 1/6. Nhưng, nếu chúng ta có một con xúc xắc bí ẩn với số mặt không xác định, thật khó để biết xác suất sẽ là bao nhiêu. Thay vì phân tích viên xúc xắc, chúng ta có thể tung nó nhiều lần và đếm số lần các sự kiện nhất định đang xảy ra.

Hãy xem cách chúng ta có thể thực hiện phương pháp này. Đầu tiên, chúng tôi sẽ cố gắng tạo ra số 1 với xác suất 10% cho một triệu lần và đếm chúng:

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

Khi đó, tổng các số được tạo chia cho số mẫu sẽ là một giá trị gần đúng của xác suất của sự kiện:

int monteCarloProbability = (howManyTimesInvoked * 100) / numberOfSamples; 

Hãy nhớ rằng, xác suất được tính là gần đúng. Số lượng mẫu càng cao thì giá trị gần đúng càng tốt.

4. Phân phối khác

Sự phân bố đồng đều hoạt động tốt để tạo mô hình những thứ như trò chơi. Để trò chơi trở nên công bằng, tất cả các sự kiện thường cần có cùng một xác suất xảy ra.

Tuy nhiên, trong cuộc sống thực, các bản phân phối thường phức tạp hơn. Cơ hội không bằng nhau cho những điều khác nhau xảy ra.

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

Kết quả là, chúng ta sẽ nhận được xác suất của một người có chiều cao giữa hai giới hạn. Trong trường hợp này, chiều cao thấp hơn và chiều cao trên.

5. Kết luận

Trong bài viết này, chúng ta đã học cách tạo các sự kiện ngẫu nhiên và cách tính xác suất chúng xảy ra. Chúng tôi đã sử dụng các phân phối đồng nhất và bình thường để mô hình hóa các tình huống khác nhau.

Ví dụ đầy đủ có thể được tìm thấy trên GitHub.