算法思想
这种算法思想最为简单。将n个奖品编号0 - N-1,其中各类奖品的概率通过其数量体现,最后程序产生0~n-1之间的随机数便是抽中的奖品编号。例如:
苹果手机概率1%,网站会员20%,折扣券20%,很遗憾59%。这样,编号0是苹果手机,1-20是会员,21-40是折扣券,41~100是 很遗憾。产生的随机数落在那个区间,就代表那个奖品被抽中。
存在问题
1、总数N快速膨胀
概率通过数量来体现在各个奖品概率较大的情况下,总数n可以较小。但如果在精度很高的情况下,总数必须按比例成倍扩大。
例如,所有奖品概率都是10%,那么n只需要取10就可以。但是如果某个奖品概率是0.01%,按照这种算法,总数要扩大到100*100。
2、平衡性影响
在Java中,Math.random()方法本身基本可以保证大量测试的情况下避免高重复,且概率分布比较平均。但是需要注意的是,该方法默认返回0-1之间的数据。
在当前算法中,必须扩大指定倍数并且强制使用int进行类型转换。在这样的扩大和转换过程中,必然会对数据精度进行修改,转换后的数据也不能保证概率分布平均。
因此,该算法实际可能达不到预期的概率要求。
3、算法复杂度
数据准备阶段,为每个奖品确定编号与奖品信息的关系集合需要O(n);
产生随机数阶段并转换,O(1);
从集合中查找,不同的数据结构实现不同,最差需要O(n);
/**
* 获取正式的计算map
* @param prizeAmountMap 金额:百分比(int)
* @param prizeTotalCountMap 金额:总共可发个数
* @param prizeRealCountMap 金额:实际已发个数
* @return
*/
public static Map<Integer, Integer> getPrizeAmountMap(Map<Integer, Integer> prizeAmountMap, Map<Integer, Integer> prizeTotalCountMap, Map<Integer, Integer> prizeRealCountMap) {
Iterator<Map.Entry<Integer, Integer>> it = prizeAmountMap.entrySet().iterator();
while(it.hasNext()){
Map.Entry<Integer, Integer> prizeItem = it.next();
Integer prizeRealCount = prizeRealCountMap.get(prizeItem.getKey());
Integer prizeTotalCount = prizeTotalCountMap.get(prizeItem.getKey());
if(prizeRealCount >= prizeTotalCount){
it.remove();
}
}
return prizeAmountMap;
}
/**
* 采用随机数抽奖方式 - 产生一个中奖金额(int)
* @param prizeAmountMap
* @return
*/
public static Integer getPrizeAmount(Map<Integer, Integer> prizeAmountMap) {
//初始化中奖金额
Integer prizeAmount = (Integer) prizeAmountMap.keySet().toArray()[0];
//遍历map获取概率int总和并且变形map的value
Integer scaleSum = 0;
for (Map.Entry<Integer, Integer> prizeItem : prizeAmountMap.entrySet()) {
scaleSum += prizeItem.getValue();
prizeAmountMap.put(prizeItem.getKey(), scaleSum);
}
//生成中奖随机数
Integer prizeRand = new Random().nextInt(scaleSum);//[0,scaleSum)
for (Map.Entry<Integer, Integer> prizeItem : prizeAmountMap.entrySet()) {
if(prizeItem.getValue() > prizeRand){
prizeAmount = prizeItem.getKey();
break;
}
}
return prizeAmount;
}
/**
* 字符串转MAp
* @param data
* @return
*/
public static Map<Integer, Integer> toIntegerMap(String data) {
Map<Integer, Integer> integerMap = new LinkedHashMap<>();
String[] itemArray = data.split(",");
for(int i=0; i<itemArray.length; i++) {
String[] itemData = itemArray[i].split(":");
integerMap.put(Integer.valueOf(itemData[0]),Integer.valueOf(itemData[1]));
}
return integerMap;
}
public static void main(String[] args) {
for(int i=0; i<10000; i++) {
//为避免修改金额的问题,修改金额在数据上是增加项
// 如188修改为168, 188:5000,288:3000,388:1500,688:500 -> 168:5000,188:0,288:3000,388:1500,688:500
//188:500,288:400,388:300,688:100 -> 168:500-188已发数量,188:已发数量,288:400,388:300,688:100
//获取需要计算的数据组装map
String prizeAmountStr = "188:5000,288:3000,388:1500,688:500";
Map<Integer, Integer> prizeAmountMap = toIntegerMap(prizeAmountStr);
//采用随机数抽奖方式 - 后续需要若该金额奖励已经没有了,自动重新抽奖
//共可发红包数量
String prizeTotalCountStr = "188:500,288:400,388:300,688:100";
Map<Integer, Integer> prizeTotalCountMap = JacksonUtil.toIntegerMap(prizeTotalCountStr);
//实际已发红包数量
Map<Integer, Integer> prizeRealCountMap = new LinkedHashMap<>();
prizeRealCountMap.put(188, 450);
prizeRealCountMap.put(288, 350);
prizeRealCountMap.put(388, 250);
prizeRealCountMap.put(688, 80);
prizeAmountMap = getPrizeAmountMap(prizeAmountMap, prizeTotalCountMap, prizeRealCountMap);
System.out.println(getPrizeAmount(prizeAmountMap));
}
}