03-抽奖系统核心算法设计

image-20201107231109595

参考资料:https://segmentfault.com/a/1190000004502605

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
import org.testng.collections.Lists;

import java.util.Arrays;
import java.util.List;
import java.util.Random;

/**
* @Class: TestLotteryAlgo
* @Description: 抽奖核心算法测试demo
* @Author: Jerry(姜源)
* @Create: 2020/11/04 10:40
*/
@SuppressWarnings("all")
public class TestLotteryAlgo {
/**
* 测试单用户抽奖1000次 或 1000个用户参与抽奖(测试0.1%概率的奖品可中可不中)
*/
private static int testNum = 1000;

public static void main(String[] args) throws InterruptedException {
// PS:可限制用户中奖次数,也可限制奖品库存抽完即止,逻辑基本一致
// 比如此 demo 限制库存,抽完即止
List<Product> products = Lists.newLinkedList();
products.add(new Product("100元通用优惠券", "5", 5));
products.add(new Product("iPhone 12 pro max!!!", "0.1", 1)); //0.1%
products.add(new Product("1元抵10元优惠券", "20", 10));
System.out.println("before = " + Arrays.toString(products.toArray())); //快速输出List元素

// 奖品按照中奖概率倒叙排列
products.sort((e1, e2) -> Double.parseDouble(e1.getpProb()) < Double.parseDouble(e2.getpProb()) ? 1 : -1);
System.out.println("after = " + Arrays.toString(products.toArray())); //快速输出List元素

int seed = 100000; // 随机数种子 10w
int i = 0; // 计数:iPhone 12 pro max
int j = 0; // 计数:100元通用优惠券
int k = 0; // 计数:1元抵10元优惠券

for (int n = 1; n <= testNum; n++) {
Random random = new Random();
int randNum = random.nextInt(seed);
String result = lottery(products, randNum, seed);
if (result != null && !result.isEmpty()) {
if (products.get(2).getpName().equals(result)) {
if (i < products.get(2).getpInventory()) {
i++;
System.out.println("第 " + n + " 人(次)抽中了 " + result);
}
} else if (products.get(1).getpName().equals(result)) {
if (j < products.get(1).getpInventory()) {
j++;
System.out.println("第 " + n + " 人(次)抽中了 " + result);
}
} else {
if (k < products.get(0).getpInventory()) {
k++;
System.out.println("第 " + n + " 人(次)抽中了 " + result);
}
}
} else {
//System.out.println("感谢参与");
}
}
System.out.println("iPhone 12 pro max 中奖次数 = " + i);
System.out.println("100元通用优惠券 中奖次数 = " + j);
System.out.println("1元抵10元优惠券 中奖次数 = " + k);
// 所有中奖奖品数量总和 == 所有奖品的库存总和
if ((i + j + k) == products.stream().mapToInt(e -> e.getpInventory()).sum()) {
System.out.println("所有奖品已全部抽完,欢迎下次再来!!!");
} else {
System.out.println("还有奖品未抽完,请继续抽奖或增加抽奖次数。");
}
}

/**
* 根据奖项概率对比出所中奖项
* 奖品按照概率倒叙排列,依次根据概率算出所对应的奖品的中奖区间,随机数在哪个区间就中哪个奖品
*
* @param awards
* @param randNum
* @param seed
* @return
*/
public static String lottery(List<Product> awards, int randNum, int seed) {
int index = 0;
for (int i = 0; i < awards.size(); i++) {
double lowLimit = 0;
double upLimit = 0;
// 概率最大的奖品中奖逻辑:随机数是否在 0 到 概率×种子 的数之间
if (i == 0) {
double winRate = Double.parseDouble(awards.get(i).getpProb()) / 100;
if (0 < randNum && randNum <= winRate * seed) {
index = i;
}
} else {
// lowLimit 累计了 0~i 之间的奖品概率值
for (int j = 0; j < i; j++) {
lowLimit += Double.parseDouble(awards.get(i).getpProb()) / 100;
}
// upLimit 累计了 0~i+1 之间的奖品概率值
for (int k = 0; k < i + 1; k++) {
upLimit += Double.parseDouble(awards.get(i).getpProb()) / 100;
}
// upLimit - lowLimit == 当前 i 的中奖概率值
// i > 0 对应下标的奖品中奖逻辑:随机数在 低累计值×种子 到 高累计值×种子 的数之间
if (lowLimit * seed < randNum && randNum <= upLimit * seed) {
index = i;
}
}
}
// 如果没有中奖,则index=0即为奖品列表中概率最大的奖品(比如优惠券,可以促进用户消费)
return awards.get(index).getpName();
}
}

class Product {
/**
* 奖品名称
*/
private String pName;
/**
* 中奖概率(x%) pProb == x
*/
private String pProb;
/**
* 库存数量
*/
private Integer pInventory;

public Product() {
}

public Product(String pName, String pProb, Integer pInventory) {
this.pName = pName;
this.pProb = pProb;
this.pInventory = pInventory;
}

public String getpName() {
return pName;
}

public void setpName(String pName) {
this.pName = pName;
}

public String getpProb() {
return pProb;
}

public void setpProb(String pProb) {
this.pProb = pProb;
}

public Integer getpInventory() {
return pInventory;
}

public void setpInventory(Integer pInventory) {
this.pInventory = pInventory;
}

@Override
public String toString() {
return "Product{" +
"pName='" + pName + '\'' +
", pProb='" + pProb + '\'' +
", pInventory=" + pInventory +
'}';
}
}

随缘运行一次(1000个用户抽了奖):

image-20201107232340054

运行一次未中大奖的情况(还是默认1000个人抽奖):

image-20201107232409178


03-抽奖系统核心算法设计
https://janycode.github.io/2020/11/04/15_项目设计/01_业务设计/03-抽奖系统核心算法设计/
作者
Jerry(姜源)
发布于
2020年11月4日
许可协议