package com.example.luckdraw;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import lombok.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
/**
* 抽奖逻辑
*/
public class LuckDrawBox {
private static final Logger logger = LoggerFactory.getLogger(LuckDrawBox.class);
/**
* 抽奖
*
* @param users 抽奖用户
* @param prizes 待分配的奖品奖品
* @return 用户和奖品关联关系
*/
public Map<User, Prize> drawPrize(List<User> users, List<Prize> prizes) {
logger.info("LuckDrawBoxImpl drawPrize[抽奖] start users:{}, prizes:{}", users == null ? null : users.size(), prizes == null ? null : prizes.size());
Map<User, Prize> result = new HashMap<>(users == null ? 0 : users.size());
if (users == null || users.size() == 0 || prizes == null || prizes.size() == 0) {
logger.info("LuckDrawBoxImpl drawPrize[抽奖] 用户或奖品不存在");
return result;
}
// 奖品处理为奖品包装类
List<PrizeWrapper> prizeWrappers = getPrizeWrappers(prizes);
drawByPrizeProbability(users, result, prizeWrappers);
logger.info("LuckDrawBoxImpl drawPrize[抽奖] end result:{}", JSON.toJSONString(result, SerializerFeature.DisableCircularReferenceDetect));
return result;
}
/**
* 抽人
*
* @param users 抽奖用户
* @param prizes 待分配的奖品
* @return 用户和奖品关联关系
*/
public Map<User, Prize> drawPeople(List<User> users, List<Prize> prizes) {
logger.info("LuckDrawBoxImpl drawPeople[抽人] start users:{}, prizes:{}, poolSize:{}", users == null ? null : users.size(), prizes == null ? null : prizes.size());
Map<User, Prize> result = new HashMap<>(users == null ? 0 : users.size());
if (users == null || users.size() == 0 || prizes == null || prizes.size() == 0) {
logger.info("LuckDrawBoxImpl drawPeople[抽人] 用户或奖品不存在");
return result;
}
// 奖品处理为奖品包装类
List<PrizeWrapper> prizeWrappers = getPrizeWrappers(prizes);
// 抽奖用户放入到奖池
List<User> usersPool = new ArrayList<>(users);
// 抽人
Random random = new Random();
int seed;
for (PrizeWrapper item : prizeWrappers) {
for (int i = item.getStart(), length = item.getEnd() + 1; i < length; i++) {
if (usersPool.size() == 0) {
break;
}
seed = random.nextInt(usersPool.size());
// 中奖用户
logger.info("LuckDrawBoxImpl drawPeople[抽人] 用户 {} 抽中奖品 {}", usersPool.get(seed).getPin(), item.getPrize().getName());
result.put(usersPool.get(seed), item.getPrize());
// 移除中奖用户 暂不考虑多次中奖机会
usersPool.remove(usersPool.get(seed));
}
}
logger.info("LuckDrawBoxImpl drawPeople[抽人] end result:{}", JSON.toJSONString(result, SerializerFeature.DisableCircularReferenceDetect));
return result;
}
/**
* 处理为奖品包装类
*
* @param prizes 奖品
* @return 奖品包装类
*/
private List<PrizeWrapper> getPrizeWrappers(List<Prize> prizes) {
List<PrizeWrapper> prizeWrappers = new ArrayList<>(prizes.size());
int prizePoint = 0;
for (Prize item : prizes) {
// 剩余奖品数量
int remainder = item.getQuantity() - item.getWinningQuantity();
logger.info("LuckDrawBoxImpl drawPrize[抽奖] {} 剩余奖品数量 {}", item.getName(), remainder);
if (remainder == 0) {
continue;
}
PrizeWrapper prizeWrapper = new PrizeWrapper();
prizeWrapper.setStart(prizePoint);
// 下一个奖品的开始点
prizePoint += remainder;
prizeWrapper.setEnd(prizePoint - 1);
prizeWrapper.setPrize(item);
prizeWrappers.add(prizeWrapper);
}
return prizeWrappers;
}
/**
* 基于奖品概率抽奖,每个奖品独立抽奖机会
*
* @param users 用户
* @param result 抽奖结果
* @param prizeWrappers 奖品包装类
*/
private void drawByPrizeProbability(List<User> users, Map<User, Prize> result, List<PrizeWrapper> prizeWrappers) {
// 默认奖池基数
int poolSize = 1000000;
Random random = new Random();
for (User user : users) {
Prize winnerPrize = null;
for (PrizeWrapper prizeWrapper : prizeWrappers) {
int seed = random.nextInt(poolSize);
if (prizeWrapper.quantity > 0 && seed < prizeWrapper.getPrize().getProbability()) {
winnerPrize = prizeWrapper.getPrize();
prizeWrapper.quantity--;
break;
}
}
if (winnerPrize != null) {
logger.info("LuckDrawBoxImpl drawPrize[抽奖] 用户 {} 抽中奖品 {}", user.getPin(), winnerPrize.getName());
} else {
logger.info("LuckDrawBoxImpl drawPrize[抽奖] 用户 {} 没有抽中奖品", user.getPin());
}
result.put(user, winnerPrize);
}
}
/**
* 基于基数抽奖,奖品数量代表不同的机会
*
* @param users 用户
* @param poolSize 奖池基数
* @param result 抽奖结果
* @param prizeWrappers 奖品包装类
*/
private void drawByBaseNumber(List<User> users, int poolSize, Map<User, Prize> result, List<PrizeWrapper> prizeWrappers) {
// 默认奖池基数
if (poolSize == 0) {
poolSize = 10000;
}
// 开始抽奖
Random random = new Random();
for (User user : users) {
int seed = random.nextInt(poolSize);
Prize winnerPrize = null;
for (PrizeWrapper prizeWrapper : prizeWrappers) {
if (seed >= prizeWrapper.getStart() && seed <= prizeWrapper.getEnd()) {
winnerPrize = prizeWrapper.getPrize();
break;
}
}
if (winnerPrize != null) {
logger.info("LuckDrawBoxImpl drawPrize[抽奖] 用户 {} 抽中奖品 {}", user.getPin(), winnerPrize.getName());
} else {
logger.info("LuckDrawBoxImpl drawPrize[抽奖] 用户 {} 没有抽中奖品", user.getPin());
}
result.put(user, winnerPrize);
}
}
/**
* 奖品包装类
*/
@Data
private class PrizeWrapper {
private int start;
private int end;
private int quantity;
private Prize prize;
}
/**
* 用户
*/
@Data
private class User {
private String pin;
private String name;
}
/**
* 奖品
*/
@Data
private class Prize {
private String name;
private int quantity;
private int winningQuantity;
private int probability;
}
}
抽奖ER图.drawio
<mxfile modified="2019-07-05T06:27:35.573Z" host="www.draw.io" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/8.8.0 Chrome/61.0.3163.100 Electron/2.0.2 Safari/537.36" etag="nJc6-QYmv5ewmsKv2WCB" version="10.8.9" type="device"><diagram id="3K_Z6mexA1ALRj9mF4yM" name="Page-1">7Vtbc6s2EP41ejwd7pdHwE7amZzOmebhzPRNAdmokZEH5NjOr68EAgzIsXNiG0+L40nQshLSfrurT0IBZrTaPeZwnX6nCSLA0JIdMGfAMHTTt/gfIdlXEs+QgmWOE6nUCp7xO5JCTUo3OEFFR5FRShhed4UxzTIUs44M5jnddtUWlHSfuoZLNBA8x5AMpT9xwtJ6FG4r/x3hZVo/WXf86s4K1spyJEUKE7o9EJlzYEY5pay6Wu0iRITxartU9R6O3G06lqOMnVNhtXlNfv65edx923uP7wn5e/YH+mY6snNsX48YJdwAskhzltIlzSCZt9Iwp5ssQaJZjZdanSdK11yoc+E/iLG9RBNuGOWilK2IvFuwnL6iiBKal880tfLT3KktLXSr/olOHR23FBV0k8fog8HW/gPzJWIf6ZkNPNyvEV0hlu95xRwRyPBbtyNQOtiy0Wsx4BcShk9AItt9g2QjnwTmDvACEM7A3AaBDXwHzF3AIyvwylseCN0BjMUWrwjMBF4LmrEaUWHkOMUkeYJ7uhFWKBiMX+tSmNIcv3N92GIFcyaRNBzRGiakhi6j5QPaSs+iMfmYHBW82o8aKL0n+g53HcUnWLC6g5QQuC7wS9llUXHFMcNZSBmjK6kkzYRyhnYfu8YQSVnB4qYrq8jkpDuyie1BqMvwTQ+i3PWuhL0xwH6NswG0fMBsEEUSCgU6kOBlxosELUQ1YTHME1wgxUwEbVisYYyz5VOpM7NayV9y2EJEed0FKZNYipMEZWUyYJDBl8a51hRnrLSLHfIvt16k/WYDm3c84mW9LfOvUM9ZRDM+FohL0BB3gy0SrnAewsdjaAi7hNlwzkK5Vrt8hA9hVoW4A4IIBLq48G0QapMfXNgPbGNcP7AVbmAD7wEEDwp/GMzThHASJIDlzGIthAndcPvPG/nBjJvAIm1m7G2KGRKwikpbztuEcxTrij4t8E4odhyIz9CLBXLiWDV3J67/ol0oIdt2NyFb1gAhR4FQLbs4Qo4SoXAOPPsAIQuEgfiOjdDCOIKQ8+LYzoUQ8ntTpqaNC5GrgMgHnlmSI1uQI8+5v2jyYqTG6sWzLftC0eTofXrjj4uVpw4nF4SqCXBciJCe2MhVQeQ7rgmvFE6G6Y0LUb3u7mEU8BxXM5GaNI8XPbb4UUHjlB+55DmQV5/rzFGGP6STt4XMPJNOhhzEsEyKgciO04rx8+DrjtVNqdYwXlUcUtesa6FvKdHvYc0vfA14M7FxELjTYuKXFhNHtoXuZVWpWk4MHcEVibyS+JzHhpMjXNoRxl5W6upVC1+yhCaYcz6si71DTpJ9V6YGvnbxhzuIkyN80RHc8+aGqzmCaQyJ2n93c1868L3v7qvWqw1H479NEZ8TNfs8NTPsLjUz9eHm0U0383X1crefh0/y8ikPn8zD7n0zM/+cmJ+Y2dUdYWxmZlr/owm5Oa5xckI+AtptJmTTnjA5+8XZbSCpG+7kSz5HeiA43NDixbnYyRALmJnYmJxY0+dZU3NA615Yk2EowD952mWaGk9Njcf84E44kqHaxD65Lppg/yLsYzMiQ7177UfA8+/mddNNXtb2j6IZI78INNSnU6YXgccQM7Th4cHbIqbYb+jjE2/yt9Lkwv4oSwJxRJoXYwKLAscqklpTUfW7cD+KHh4+QVKH1jwwl60wVy07m3rKJ/wQ2bcFy9F6aPXex1XUWVZqgRi20ztTYbq9hipqPWiIGxruD9Tk5HC0v+7gaKrVc5CqxdZdGpN+wYMUGxWTBx1L0MaveVA/bbi9di7kQGavu57xFf/hxfY/FSr19v89zPm/</diagram></mxfile>
网友评论