摇号中签生成随机号

28 篇文章 2 订阅
2 篇文章 0 订阅

  在现在很多类似于股票市场的交易中,很多项目发行都需要进行申购,等到申购结束,进行摇号,根据中签尾号确定每个用户的中签数量。

如果用户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都不会中签。

算法原理:

  1. 计算中签率R=12345/675893=0.018264...
  2. 拆分小数位,百分位是为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就是用户的中签数量。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值