在现在很多类似于股票市场的交易中,很多项目发行都需要进行申购,等到申购结束,进行摇号,根据中签尾号确定每个用户的中签数量。
如果用户U1购买了10个产品,那么他申购的产品尾号就是10000001到10000010,用户U2再购买5个,那么U2的产品尾号10000011到10000015。
现在假如发行项目A,发行量为12345,申购量为675893。随机生成中签尾号:
package com.fbd.core.util;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import com.fbd.core.exception.ApplicationException;
/**
* 摇号中签工具类 生成中签号码
*
* @author Lip
*
*/
public class LotterySystem {
// 已经选中的尾号的数量
// public static long chooseNum = 0;
public static void main(String[] args) {
// 起始申购序列号
long start = 10000000001L;
// 实际申购数量
long purchaseNum =675893;
// 实际发行
long distributeNum = 12345;
Map<String, Integer> distributeMap = getLottery(purchaseNum, distributeNum);
int total = 0;
Iterator iterator = distributeMap.entrySet().iterator();
while (iterator.hasNext()) {
Entry entry = (Entry) iterator.next();
System.out.println(entry.getKey() + ":" + entry.getValue());
total += (int) entry.getValue();
}
System.out.println("中签数量:" + total);
}
/**
* 得到各个尾数的中签数量
*
* @param purchaseNum
* @param distributeNum
* @return
*/
public static Map<String, Integer> getLottery(long purchaseNum, long distributeNum) {
// 中签尾数及数量
Map<String, Integer> distributeMap = new LinkedHashMap<>();
if (purchaseNum <= distributeNum) {
int n1 = (int) (purchaseNum % 10);
int n2 = (int) (purchaseNum / 10);
for (int i = 0; i < 10; i++) {
if (i >= n1)
distributeMap.put(i + "", n2);
else
distributeMap.put(i + "", n2 + 1);
}
return distributeMap;
}
long chooseNum = 0;
double allocationRate = distributeNum * 1.0 / purchaseNum;// 0.001204177...
System.out.println("中签率:" + allocationRate);
int len = getDigitNum(purchaseNum);
long distributeX = (long) (allocationRate * Math.pow(10, len));// 1204177
List<Integer> digitList = getEachDigit(distributeX, len);// 1,2,0,4,1,7,7
int lenX = getDigitNum(distributeX);
List<Long> distributeList = new ArrayList<>();
for (int i = 0; i < digitList.size(); i++) {
int rate = digitList.get(i);
// 尾号余数如232,158 也可以中奖
long temp = (long) (purchaseNum % Math.pow(10, len - lenX + 1 + i));
for (int j = 0; j < rate; j++) {
if (chooseNum == distributeNum)
return distributeMap;
// 该随机号有多少个
String lotteryNum = getRandom(distributeList, len - lenX + 1 + i);
int number = (int) (purchaseNum * Math.pow(10, -(len - lenX + 1 + i)));
long lotteryLong = Long.parseLong(lotteryNum);
if (lotteryLong <= temp && lotteryLong > 0) {
number++;
}
if (chooseNum + number <= distributeNum)
chooseNum += number;
else
break;
distributeList.add(lotteryLong);
distributeMap.put(lotteryNum, number);
}
}
int left = (int) (distributeNum - chooseNum);
while (left > 0)// 每次产生一个号码
{
String lotteryNum = getRandom(distributeList, len);
long lotteryLong = Long.parseLong(lotteryNum);
if (lotteryLong > purchaseNum || lotteryLong == 0) {
continue;
}
distributeList.add(lotteryLong);
distributeMap.put(lotteryNum, 1);
left--;
}
return distributeMap;
}
/**
* 得到一个数的位数
*
* @param value
* @return
*/
public static int getDigitNum(long value) {
return String.valueOf(value).length();
}
/**
* 得到一个num位的随机数
*
* @param except
* @param num
* @return
*/
public static String getRandom(List<Long> except, int num) {
boolean confict = true;
long obj = 0l;
while (confict) {
obj = (long) (Math.random() * Math.pow(10, num));
while (except.contains(obj) || obj == 0) {// obj肯定不在except中
obj = (long) (Math.random() * Math.pow(10, num));
}
confict = false;
int len = getLen(obj);
for (long temp : except) {
int len2 = getLen(temp);
if (len2 == len) {
continue;
}
if (Math.abs(obj - temp) % Math.pow(10, len2) == 0) // 有冲突
{
confict = true;
break;
}
}
}
return String.format("%0" + num + "d", obj);
}
/**
* 得到一个整数的位数
*
* @param num
* @return
*/
public static int getLen(long num) {
int len = 0;
while (num != 0) {
num /= 10;
len++;
}
return len;
}
/**
* 得到每位的中签比率
*
* @param value
* @param len
* @return
*/
public static List<Integer> getEachDigit(long value, int len) {
String valueS = String.valueOf(value);
List<Integer> result = new ArrayList<>();
for (int i = 0; i < valueS.length() - 1; i++) {
result.add(Integer.parseInt(valueS.charAt(i) + ""));
}
return result;
}
}
生成的中签尾号完全是随机的,如下图:
有一个特殊的情况需要注意,那就是申购总量很少,小于发行量,那么相当于每个尾号都是中签的,当然,在实际中,这种情况不可能存在,出现那么也意味着该项目失败了。不过本文解决了申购量小于等于发行量的特殊情况。
该项目的中签率很低,用户U1和U2都不会中签。
算法原理:
- 计算中签率R=12345/675893=0.018264...
- 拆分小数位,百分位是为1,说明两位数的中签尾号有1个,千分位为8,说明三位数的中签尾号有8个....依次类推,直到产生足够的中签尾号
摇号结束后,知道了所有的中签尾号,也知道每个用户的购买数,那么可以计算每个用户的中签数量。我是利用存储过程来计算用户的中签数量的:
BEGIN
DECLARE v_num varchar(11);
DECLARE v_len int;
DECLARE done INT;
DECLARE v_result int;
DECLARE v_start_result int;
DECLARE v_end_result int;
DECLARE v_num_pow int;
DECLARE cur_success CURSOR FOR SELECT number from lottery_number where project_id=projectId;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
set v_result = 0;
set v_start_result=0;
set v_end_result=0;
OPEN cur_success;
BEGIN_success: LOOP
FETCH cur_success INTO v_num;
IF done THEN
LEAVE BEGIN_success;
ELSE
set v_len = LENGTH(v_num);
set v_num_pow=POWER(10,v_len);
set v_start_result=v_start_result+FLOOR(startNum/v_num_pow);
IF startNum % v_num_pow>v_num THEN
set v_start_result=v_start_result + 1;
END IF;
set v_end_result=v_end_result+FLOOR(endNum/v_num_pow);
IF endNum%v_num_pow>=v_num THEN
set v_end_result=v_end_result+1;
END IF;
END IF;
END LOOP BEGIN_success;
CLOSE cur_success;
SET v_result=v_end_result-v_start_result;
RETURN v_result;
END
原理其实很简单,每个用户都有一个起始配号,一个结束配号,那么只需要计算0到起始配号之间的中签数量n1,再计算0到结束配号之间的中签数量n2,那么n2-n1+1就是用户的中签数量。