基于概率的公平抽奖、公开开奖算法

背景

最近,由于项目需要,在产品同事的合作下专门设计并开发了一个基于概率的抽奖、开奖程序。我们先看下需求:

需求

基于现有的用户积分信息开发一套世界杯抽奖、开奖程序。首先,每个用户每天完成日常任务后可参与现金红包抽奖,中奖概率为随机的,不做人为隐形设定。但是系统可以配置现金奖池大小,根据奖池大小,每天最多发出相应金额的红包。其次,现金红包有不同的金额范围限制,不同的范围中奖概率不同。另外,根据用户操作等,积分榜是实时变动的。活动结束后积分榜前三名和积分榜4-50中随机抽取的一名,共4名小伙伴可获得系统大奖。设计一个公开、公正的算法从第4名至第50名中随机抽取一个幸运儿。

设计

由于奖池和红包部分相对复杂,所以编写程序处理;随机抽取幸运儿相对简单,利用第三方数据源和独立公开算法计算出幸运儿名次即可。

幸运的宝宝

这部分相对简单,先说下规则:

LuckyNumber = Parameter % (50 - 3) + 4

Parameter取值为 中国银行美元外汇牌价 2018/07/16 12:00前(世界杯结束后,平台于07/16中午公开开奖)最后一条记录的现汇买入价去小数点后的值;为了保证公平性或防止意外,可从多平台取值,即LuckyNumber = (Parameter1 + Parameter2 + Parameter3) % (50 - 3) + 4

50 - 3的意思是只有47人参与抽奖

余数是从0开始的,而中奖者序号从4开始,所以+4

  • 举例
    假如2018/07/16查询得到如下外汇信息:
    这里写图片描述
    则:LuckyNumber = Parameter % (50 - 3) + 4 = 66132 % 47 + 4 = 3 + 4 = 7
    榜单第7名即为幸运用户。

红包抽奖

配置

  • level=N # 0-9
  • level.prize=p0, p1, p2, … , pn
  • level.rate=r0, r1 … , rn-1

配置示例

  • level=3
  • level.amount=100, 1000, 9000, 10000
  • level.rate=100, 20, 10

示例含义

红包分为三个等级,[1.00, 10.00)的概率是1%(100/10000),[10.00-90.00)的概率是0.2%,[90.00-100.00]的概率是0.1%。

示例程序

package test;

import java.util.Random;

import com.alibaba.fastjson.JSONObject;

public class T {
    private static final int LEVEL = 3;
    private static final int[] LEVEL_AMOUNT = new int[] { 100, 1000, 9000, 10000 };
    private static final int[] LEVEL_RATE = new int[] { 100, 20, 10 };

    public static void main(String[] args) {
        System.out.println(prize());
    }

    /**
     * {"lucky":9282,"prize":false}
     * {"lucky":50,"amount":980,"level":0,"prize":true}
     * lucky:幸运数
     * prize:中奖标识,true-中奖,false-没中奖
     * level:中奖等级
     * amount:中奖金额
     * @return
     */
    public static JSONObject prize() {
        JSONObject result = new JSONObject();
        Random r = new Random();

        // 服务器每天第一次收到抽奖请求或相关请求时初始化当日奖池信息,
        // 或定时器每天自动初始化奖池信息(凌晨高并发请求时可能查不到当日奖池信息)

        // 生成幸运数
        int lucky = r.nextInt(10000);
        result.put("lucky", lucky);

        // 声明中奖等级,默认不中奖,值为-1;或中奖值为0,1,2
        int level = -1;

        // 若lucky < level.rate0,则level=0;
        // 否则,若lucky < level.rate0 + level.rate1,则level=1
        // ...
        int rate = 0;
        for (int i = 0; i < LEVEL_RATE.length; i++) {
            rate += LEVEL_RATE[i];
            if (lucky < rate) {
                level = i;
                break;
            }
        }

        if (level > -1) {
            result.put("prize", true);
            result.put("level", level);

            int amount = 0;
            // 最后一个中奖等级的奖金范围是闭区间,其他都是左闭右开
            if (level + 1 == LEVEL) {
                // nextInt(N)的范围是[0,N),nextInt(N * 10) / 10的范围是[0,N]
                int target = (int) (LEVEL_AMOUNT[level + 1] - LEVEL_AMOUNT[level] + 1) * 10;
                amount = r.nextInt(target) / 10 + (int) LEVEL_AMOUNT[level];
            } else {
                // nextInt(N-M)的范围是[0, N-M),nextInt(N-M) + M的范围是[M,N)
                int target = (int) (LEVEL_AMOUNT[level + 1] - LEVEL_AMOUNT[level]);
                amount = r.nextInt(target) + (int) LEVEL_AMOUNT[level];
            }

            int pool_unusedAmount = 1000000; // 查询当日奖池可用金额

            // 奖池可用金额不足时,按奖池剩余金额支付
            if (amount > pool_unusedAmount) {
                amount = pool_unusedAmount;
            }

            // 更新当日奖池信息,保存用户中奖数据
            result.put("amount", amount);
        } else {
            result.put("prize", false);
        }

        return result;
    }
}
©️2020 CSDN 皮肤主题: 大白 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值