在现在很多类似于股票市场的交易中,很多项目发行都需要进行申购,等到申购结束,进行摇号,根据中签尾号确定每个用户的中签数量。
如果用户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都不会中签。
算法原理:
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就是用户的中签数量。