背景
最近看項目中的概率抽獎算法實現,總感覺有問題,驗證了多遍,發現沒問題。但是代碼不夠精簡,尤其使用了多次Random,這個還是可以優化的~
那麼,如何實現高效的概率算法?如何衡量算法的準確性呢?
算法實現
特點:只隨機一次,找出命中概率的 index。
// 入參示例:int[] array = {30, 40, 20, 10};
private static int getIndex(int[] array) {
int num = new Random().nextInt(100);
for (int i = 0; i < array.length; i++) {
if (num < array[i]) {
return i;
}
num -= array[i];
}
return -1;
}
如何衡量
// 計算概率,測試10000次
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < 10000; i++) {
// 單次獲取概率結果
int index = getIndex(array);
// System.out.printf("中獎啦! 概率: %d, Index: %d%n", array[index], index);
Integer value = map.getOrDefault(array[index], 0);
map.put(array[index], value + 1);
}
System.out.println("----------------------------------------------");
System.out.printf("看看結果吧! 概率: %s%n", map);
System.out.println("----------------------------------------------");
觀察如下結果,符合按概率隨機的要求。
----------------------------------------------
看看結果吧! 概率: {20=2004, 40=3985, 10=1036, 30=2975}
----------------------------------------------
看看結果吧! 概率: {20=2053, 40=3932, 10=999, 30=3016}
----------------------------------------------
看看結果吧! 概率: {20=2044, 40=4015, 10=962, 30=2979}
----------------------------------------------
看看結果吧! 概率: {20=2040, 40=3955, 10=982, 30=3023}
----------------------------------------------
看看結果吧! 概率: {20=1968, 40=3976, 10=1009, 30=3047}
----------------------------------------------
看看結果吧! 概率: {20=1984, 40=4029, 10=989, 30=2998}
----------------------------------------------
健壯性
如果入參的總概率不滿100%,那剩餘的比例不能中獎,怎麼實現呢?
- 總概率不滿100%時,補全數組;
- 計算中獎的時候,再捨去補全的最後一組(當然,如果沒補就不用捨去)。
完整代碼如下:
package cn.eyeo.mall.start.lottery;
import org.junit.Test;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
/**
* 抽獎測試類
*
* @author <a href="mailto:[email protected]">amos.wang</a>
* @date 2023/3/27
*/
public class LotteryDrawTests {
private static final int TOTAL_PROBABILITY = 100;
@Test
public void lottery() {
int[] array = {30, 40, 20};
probability(array);
}
private static void probability(int[] source) {
int sum = Arrays.stream(source).sum();
if (sum > TOTAL_PROBABILITY) {
throw new IllegalArgumentException("總概率不能>100");
}
System.out.println("----------------------------------------------");
System.out.println("原始數據: " + Arrays.toString(source));
// 不滿100%則補全
int[] array = source;
if (sum < TOTAL_PROBABILITY) {
array = new int[source.length + 1];
System.arraycopy(source, 0, array, 0, source.length);
array[source.length] = TOTAL_PROBABILITY - sum;
}
System.out.println("補全後的數據: " + Arrays.toString(array));
// 計算概率,測試10000次
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < 10000; i++) {
// 單次獲取概率結果
int index = getIndex(array);
if (index < source.length) {
// System.out.printf("中獎啦! 概率: %d, Index: %d%n", array[index], index);
Integer value = map.getOrDefault(array[index], 0);
map.put(array[index], value + 1);
}
}
System.out.println("----------------------------------------------");
System.out.printf("看看結果吧! 概率: %s%n", map);
}
private static int getIndex(int[] array) {
int num = new Random().nextInt(TOTAL_PROBABILITY);
for (int i = 0; i < array.length; i++) {
if (num < array[i]) {
return i;
}
num -= array[i];
}
return -1;
}
}