概要
一个基于Java编程语言实现的抽奖案例小样,提供抽奖实现思路。
整体架构流程
- 抽奖活动管理
- 奖品设置
- 中奖规则
名词解释
- 权重
- 库存
- 奖池
技术细节
案例1:权重为浮点型,计算是否中奖通过[0,1) * totalWeigth与奖品权重值进行比较。
import java.util.*;
import java.util.concurrent.CountDownLatch;
public class LotterySystem {
private static List<Prize> prizes = new ArrayList<>();
public static void main(String[] args) {
// 管理员添加奖品(无需总和=1)
addPrize(1, "iPhone", 100.0, 2, "奖品");
addPrize(2, "AirPods", 100.0, 1, "奖品");
addPrize(3, "谢谢参与", 10.0, -1, "谢谢参与");
addPrize(4, "谢谢参与", 9800.0, -1, "谢谢参与");
addPrize(5, "谢谢参与", 8800.0, -1, "谢谢参与");
addPrize(6, "谢谢参与", 8090.0, -1, "谢谢参与");
addPrize(7, "谢谢参与", 8600.0, -1, "谢谢参与");
addPrize(8, "谢谢参与", 880.0, -1, "谢谢参与");
// 查看当前权重分布
prizes.forEach(prize -> System.out.printf(
"开始: %s: 原始权重=%.1f, 实际概率=%.10f%%%n",
prize.getName(), prize.getRawWeight(), prize.getNormalizedWeight() * 100
));
// 抽奖
// for (int i = 0; i < 20000; i++) {
// Prize winner = drawWinner();
// if (winner.id == 1 || winner.id == 2) {
// System.out.println("第"+ i + "次中奖奖品: " + "id:"+ winner.id + ":" +winner.getName());
// }
// }
int count = 1000;
CountDownLatch countDownLatch = new CountDownLatch(count);
// 模拟并发抽奖
for (int i = 0; i < count; i++) {
new Thread(() -> {
Prize winner = drawWinner();
if (winner.id == 1 || winner.id == 2) {
System.out.println("第"+ Thread.currentThread().getName() + "次中奖奖品: " + "id:"+ winner.id + ":" +winner.getName());
}
countDownLatch.countDown();
}).start();
}
while (countDownLatch.getCount() >= 1) {}
prizes.forEach(prize -> System.out.printf(
"结束: %s: 原始权重=%.1f, 实际概率=%.10f%%%n",
prize.getName(), prize.getRawWeight(), prize.getNormalizedWeight() * 100
));
}
public static void addPrize(int id, String name, double rawWeight, int stock, String type) {
prizes.add(new Prize(id, name, rawWeight, stock, type));
}
public static Prize drawWinner() {
double totalWeight = prizes.stream().mapToDouble(Prize::getRawWeight).sum();
int sum = prizes.stream().filter(prize -> !"谢谢参与".equals(prize.type)).mapToInt(Prize::getstock).sum();
if (sum <= 0) {
Optional<Prize> optionalPrize = prizes.stream().filter(prize -> "谢谢参与".equals(prize.type)).findAny();
if (optionalPrize.isPresent()) {
return optionalPrize.get();
}
throw new RuntimeException("当前活动太火爆,奖品已被抽完啦");
}
double random = Math.random() * totalWeight;
double cumulativeWeight = 0.0;
for (Prize prize : prizes) {
cumulativeWeight += prize.getRawWeight();
if (random < cumulativeWeight) {
if (prize.stock != -1 && prize.stock <= 0) {
prize.rawWeight = 0;
return drawWinner();
}else if (prize.stock > 0){
synchronized (Prize.class) {
if (prize.stock <= 0) {
prize.rawWeight = 0;
return drawWinner();
}
prize.stock -= 1;
if (prize.stock <= 0) {
prize.rawWeight = 0;
}
}
}
return prize;
}
}
throw new IllegalStateException("抽奖逻辑异常");
}
static class Prize {
private int id;
private String name;
private double rawWeight;
private int stock;
private String type;
public Prize(int id, String name, double rawWeight, int stock, String type) {
this.id = id;
this.name = name;
this.rawWeight = rawWeight;
this.stock = stock;
this.type = type;
}
public String getName() { return name; }
public double getRawWeight() { return rawWeight; }
public int getstock() { return stock; }
public double getNormalizedWeight() {
double totalWeight = prizes.stream().mapToDouble(Prize::getRawWeight).sum();
return rawWeight / totalWeight;
}
}
}
案例2:权重为整型,计算是否中奖通过totalWeigth基数来获取中奖数字与奖品权重值进行比较。
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
public class LotterySystem2 {
private static List<Prize> prizes = new ArrayList<>();
private static Random random = new Random();
public static void main(String[] args) {
// 管理员添加奖品(无需总和=1)
addPrize(1, "iPhone", 100, 2, "奖品");
addPrize(2, "AirPods", 100, 1, "奖品");
addPrize(3, "谢谢参与", 10, -1, "谢谢参与");
addPrize(4, "谢谢参与", 9800, -1, "谢谢参与");
addPrize(5, "谢谢参与", 8800, -1, "谢谢参与");
addPrize(6, "谢谢参与", 8090, -1, "谢谢参与");
addPrize(7, "谢谢参与", 8600, -1, "谢谢参与");
addPrize(8, "谢谢参与", 880, -1, "谢谢参与");
// 查看当前权重分布
prizes.forEach(prize -> System.out.printf(
"开始: %s: 原始权重=%d, 实际概率=%.10f%%%n",
prize.getName(), prize.getRawWeight(), prize.getNormalizedWeight() * 100
));
// 模拟抽奖
// for (int i = 0; i < 20000; i++) {
// Prize winner = drawWinner();
// if (winner.id == 1 || winner.id == 2) {
// System.out.println("第"+ i + "次中奖奖品: " + "id:"+ winner.id + ":" +winner.getName());
// }
// }
int count = 1000;
CountDownLatch countDownLatch = new CountDownLatch(count);
// 模拟并发抽奖
for (int i = 0; i < count; i++) {
new Thread(() -> {
Prize winner = drawWinner();
if (winner.id == 1 || winner.id == 2) {
System.out.println("第"+ Thread.currentThread().getName() + "次中奖奖品: " + "id:"+ winner.id + ":" +winner.getName());
}
countDownLatch.countDown();
}).start();
}
while (countDownLatch.getCount() >= 1) {}
// 查看当前权重分布
prizes.forEach(prize -> System.out.printf(
"结束: %s: 原始权重=%d, 实际概率=%.10f%%%n",
prize.getName(), prize.getRawWeight(), prize.getNormalizedWeight() * 100
));
}
public static void addPrize(int id, String name, int rawWeight, int stock, String type) {
prizes.add(new Prize(id, name, rawWeight, stock, type));
}
/**
* 抽奖
*
* @return 奖品对象
*/
public static Prize drawWinner() {
int totalWeight = prizes.stream().mapToInt(Prize::getRawWeight).sum();
int sum = prizes.stream().filter(prize -> !"谢谢参与".equals(prize.type)).mapToInt(Prize::getstock).sum();
if (sum <= 0) {
Optional<Prize> optionalPrize = prizes.stream().filter(prize -> "谢谢参与".equals(prize.type)).findAny();
if (optionalPrize.isPresent()) {
return optionalPrize.get();
}
throw new RuntimeException("当前活动太火爆,奖品已被抽完啦");
}
int number = random.nextInt(totalWeight);
int cumulativeWeight = 0;
for (Prize prize : prizes) {
cumulativeWeight += prize.getRawWeight();
if (number < cumulativeWeight) {
if (prize.stock != -1 && prize.stock <= 0) {
prize.rawWeight = 0;
return drawWinner();
}else if (prize.stock > 0){
synchronized (Prize.class) {
if (prize.stock <= 0) {
prize.rawWeight = 0;
return drawWinner();
}
prize.stock -= 1;
if (prize.stock <= 0) {
prize.rawWeight = 0;
}
}
}
return prize;
}
}
throw new RuntimeException("抽奖逻辑异常");
}
/**
* 奖品类
*
*/
static class Prize {
private int id;
private String name;
private int rawWeight;
private int stock;
private String type;
public Prize(int id, String name, int rawWeight, int stock, String type) {
this.id = id;
this.name = name;
this.rawWeight = rawWeight;
this.stock = stock;
this.type = type;
}
public String getName() { return name; }
public int getRawWeight() { return rawWeight; }
public int getstock() { return stock; }
public double getNormalizedWeight() {
int totalWeight = prizes.stream().mapToInt(Prize::getRawWeight).sum();
return (double) rawWeight / totalWeight;
}
}
}
小结
通过以上两个案例不难发现其实抽奖的业务并不复杂,当然这只是简单的抽奖活动,重要的理解就是奖池的设计、以及如何中奖,中奖后奖品的变化和奖池的变化。总结,以上只是提供实现的思路,具体用于生产还需要靠更多的业务场景和相关业务的影响。