一个简单抽奖算法的实现

最近的一个业务需求是开发一个抽奖管理功能,要求在一个奖池中放一堆奖品,分别给它们设置不同的数量和概率,在奖品没有发完的情况下,概率高的被抽中的几率就大,反之则低。另外,概率为0的不能被抽中,概率为100则一定要被抽中。

这里只讨论下其中的核心算法的设计及一个示例函数,算法之外的系统控制暂不提及。

实现抽奖的方法应该有很多,没有仔细去考察和搜索那些非常复杂的算法,这里仅做了一个简单的假设,并在此基础上推出后面所有的控制逻辑。

  1. 基本假设

假设系统生成一个1到100之间的随机整数R,C为1到100之间的一个任意整数,那么R小于C的概率为C/100。

暂且不去从数学上论证这个假设是否真的成立,我们仅从直观上来看,R是随机的,它的值不论是多少,取到1-100之间任意一个整数的概率都是一样的。

但C越小,R落入0-C之间的概率也会越小,所以我们大致上可以用C来控制某个奖品的概率。Good!

  1. 实现逻辑

有了上面的假设,我们就可以考虑实现一个奖池内不同奖品配置情况下的控制逻辑。

首先我们把奖池中的奖品做一个过滤,剔除掉那些不满足条件的奖品,比如概率为0的、已经发完的等等。剩下的奖品都是可以被抽中的,只是概率大小不同而已。

OK,下面我们来设置一些概念,以方便后面的行文。

假设有N个奖品,它们都以100为满概率,那么它们总共的概率空间为O=N*100;
如果这N个奖品的概率分别为C1,C2,C3...Cn,那么他们总共的中奖概率空间就是H=C1+C2+C3+...+Cn,因为Cn总是小于等于100,所以
H总是小于等于O。

我们把以上这些参数在后台配置好,当抽奖行为发生时,我们让系统生成一个随机数R,1<=R<=O,那么当R<=C时,我们就认为中奖了,否则就不中奖。Good10!

在判断出是否中奖后,我们就可以进一步判断中了什么奖。
首先把奖品以数组形式A按概率从小到大进行排序,然后求出每个奖品在总中奖概率空间H中的中奖区间,并且把各区间的最大值保存成一个数组D。

例如有a和b两个奖品,概率分别为20和30,那么a的概率空间为20,中奖区间为1-20;而b的概率空间为30,但它的中奖区间是20-50,这样D就是(1,20,50)。

然后我们再把D从小到大排序并循环,当R小于20时,我们认为a被抽中;R小于50时,认为b被抽中。Good11!

这里有个问题,就是为什么不直接把R跟a和b的概率比较,而要比较它们各自中间区间的最大值?

因为我们设想的情况是,不仅某种奖品概率调大时其抽中的几率增大,而且所有奖品的概率都调大时,它们被抽中的几率都增大。

如果直接把R跟奖品各自的概率比较,根据我们上面的逻辑,它们总的中奖空间H=2×100=200,只要R的值小于200,我们就已经认为中奖了;但是当a和b两种奖品的概率为99时,只有当R小于100时它们才会被抽中,R落在100到200之间将不被认为中奖,这显然是不对的。

搞清楚了上面的逻辑,剩下的就是处理一些特殊情况了。
比如,如果某些奖品的概率为100,这就是我们之前说的存在满概率奖品。按我们的设想,当有百分百中奖的奖品时,我们一定要这种奖品被抽中。

处理这个问题,我的方法是把奖品按概率分成两组,一组是满概率奖品,一组是非满概率奖品。当满概率奖品组不为空时,从中随机取出一个作为被抽中的奖品放出,直到这些奖品被抽完。

到此为止整个逻辑基本结束,可以着手写代码了。Good101!

/**
* 抽奖核心算法
* @param prize array,所有概率不为0且剩余数大于0的奖品数组 
* @return array 单个奖品
* version 2015.12.21
* author thinkmad@sina.com
*/
const FULL_CHANCE = 100;
function calcPrize($prize){
    
    if(!$prize){
        return false;
    }
    
    $arr_chance = array();//所有奖品概率
    $arr_delimiter = array();//中奖区间分界数组
    $full_chance_prize = $nofull_chance_prize = array();//划分满概率和非满概率数组
    $H = 0;//中奖概率空间
    
    //划分满概率和非满概率奖品
    foreach($prize as $item){
        if($item['prizeChance'] >= self::FULL_CHANCE) {
            $full_chance_prize[] = $item;
        }else{
            $nofull_chance_prize[$item['prizeID']] = $item;
        }
    }
    
    //存在满概率奖品,则随机取出一个奖品并返回
    $len = count($full_chance_prize);
    if($len > 0){
        $r = mt_rand(0,$len-1);
        return $full_chance_prize[$r];
    }
    
    //计算总概率空间O
    $O = count($prize) * self::FULL_CHANCE;
    
    //计算总中奖空间H并生成概率数组
    foreach($nofull_chance_prize as $k => $v){
        $H += $v['prizeChance'];
        $arr_chance[$k] = $v["prizeChance"];
    }
    
    $R = mt_rand(1,$O);
    if($R > $H){ //R不在中奖空间
        return false;
    }else{//R落在中奖空间
        asort($arr_chance);
        for($i = 0; $i < count($arr_chance) ; $i++){
            $arr_delimiter[key($arr_chance)] = array_isum($arr_chance,0,$i+1);
            next($arr_chance);
        }
        foreach($arr_delimiter as $key => $val){
            if($R <= $val) {
                return $nofull_chance_prize[$key];
            }
        }
    }
}

/**
* 辅助函数array_isum,计算数组中i起n个数的和
* @params $input array,要计算的数组
* @params $start,int,起始位置
* @params $num,int,个数
* @return int
*/
function array_isum($input,$start,$num){
    $temp = array_slice($input, $start,$num);
    return array_sum($temp);
}

上面是用PHP实现的一个核心算法,其中定义了一个常量FULL_CHANCE为100。还定义了一个辅助函数array_isum,用来计算一个数组中从下标i开始n个数的和。

这里面其实还有一个小小的问题,就是当几种奖品都是非满概率奖品且它们的概率相同,我们需要随机抽出一个作为奖品放出,如果不做这个处理,则会按照默认顺序先把前面的奖品发完再发后面的。
Enjoy It!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容

  • 你的数学直觉怎么样?你能凭借直觉,迅速地判断出谁的概率大,谁的概率小吗?下面就是 26 个这样的问题。如果你感兴趣...
    cnnjzc阅读 6,437评论 0 12
  • 一般的抽奖管理功能,基本是在一个奖池中放一堆奖品,分别给它们设置不同的数量和概率,在奖品没有发完的情况下,概...
    wwking02阅读 3,736评论 1 4
  • 一般的抽奖管理功能,基本是在一个奖池中放一堆奖品,分别给它们设置不同的数量和概率,在奖品没有发完的情况下,...
    wwking阅读 9,947评论 3 16
  • 孝青老师逝世一周年祭奠 敬爱的老师: 您在天国365日里好吗?去年的今日您来不及与学生们道别,顺着...
    黄相英阅读 385评论 2 1
  • 2017.11.23日 星期四 天气晴 11月23日清晨音频 各位同学,大家早上好,今天是我们100天精华内容领读...
    X先生_未知数的X阅读 465评论 0 0