最近的一個(gè)業(yè)務(wù)需求是開發(fā)一個(gè)抽獎(jiǎng)管理功能,要求在一個(gè)獎(jiǎng)池中放一堆獎(jiǎng)品,分別給它們?cè)O(shè)置不同的數(shù)量和概率,在獎(jiǎng)品沒有發(fā)完的情況下,概率高的被抽中的幾率就大,反之則低。另外,概率為0的不能被抽中,概率為100則一定要被抽中。
這里只討論下其中的核心算法的設(shè)計(jì)及一個(gè)示例函數(shù),算法之外的系統(tǒng)控制暫不提及。
實(shí)現(xiàn)抽獎(jiǎng)的方法應(yīng)該有很多,沒有仔細(xì)去考察和搜索那些非常復(fù)雜的算法,這里僅做了一個(gè)簡(jiǎn)單的假設(shè),并在此基礎(chǔ)上推出后面所有的控制邏輯。
- 基本假設(shè)
假設(shè)系統(tǒng)生成一個(gè)1到100之間的隨機(jī)整數(shù)R,C為1到100之間的一個(gè)任意整數(shù),那么R小于C的概率為C/100。
暫且不去從數(shù)學(xué)上論證這個(gè)假設(shè)是否真的成立,我們僅從直觀上來(lái)看,R是隨機(jī)的,它的值不論是多少,取到1-100之間任意一個(gè)整數(shù)的概率都是一樣的。
但C越小,R落入0-C之間的概率也會(huì)越小,所以我們大致上可以用C來(lái)控制某個(gè)獎(jiǎng)品的概率。Good!
- 實(shí)現(xiàn)邏輯
有了上面的假設(shè),我們就可以考慮實(shí)現(xiàn)一個(gè)獎(jiǎng)池內(nèi)不同獎(jiǎng)品配置情況下的控制邏輯。
首先我們把獎(jiǎng)池中的獎(jiǎng)品做一個(gè)過濾,剔除掉那些不滿足條件的獎(jiǎng)品,比如概率為0的、已經(jīng)發(fā)完的等等。剩下的獎(jiǎng)品都是可以被抽中的,只是概率大小不同而已。
OK,下面我們來(lái)設(shè)置一些概念,以方便后面的行文。
假設(shè)有N個(gè)獎(jiǎng)品,它們都以100為滿概率,那么它們總共的概率空間為O=N*100;
如果這N個(gè)獎(jiǎng)品的概率分別為C1,C2,C3...Cn,那么他們總共的中獎(jiǎng)概率空間就是H=C1+C2+C3+...+Cn,因?yàn)镃n總是小于等于100,所以
H總是小于等于O。
我們把以上這些參數(shù)在后臺(tái)配置好,當(dāng)抽獎(jiǎng)行為發(fā)生時(shí),我們讓系統(tǒng)生成一個(gè)隨機(jī)數(shù)R,1<=R<=O,那么當(dāng)R<=C時(shí),我們就認(rèn)為中獎(jiǎng)了,否則就不中獎(jiǎng)。Good10!
在判斷出是否中獎(jiǎng)后,我們就可以進(jìn)一步判斷中了什么獎(jiǎng)。
首先把獎(jiǎng)品以數(shù)組形式A按概率從小到大進(jìn)行排序,然后求出每個(gè)獎(jiǎng)品在總中獎(jiǎng)概率空間H中的中獎(jiǎng)區(qū)間,并且把各區(qū)間的最大值保存成一個(gè)數(shù)組D。
例如有a和b兩個(gè)獎(jiǎng)品,概率分別為20和30,那么a的概率空間為20,中獎(jiǎng)區(qū)間為1-20;而b的概率空間為30,但它的中獎(jiǎng)區(qū)間是20-50,這樣D就是(1,20,50)。
然后我們?cè)侔袲從小到大排序并循環(huán),當(dāng)R小于20時(shí),我們認(rèn)為a被抽中;R小于50時(shí),認(rèn)為b被抽中。Good11!
這里有個(gè)問題,就是為什么不直接把R跟a和b的概率比較,而要比較它們各自中間區(qū)間的最大值?
因?yàn)槲覀冊(cè)O(shè)想的情況是,不僅某種獎(jiǎng)品概率調(diào)大時(shí)其抽中的幾率增大,而且所有獎(jiǎng)品的概率都調(diào)大時(shí),它們被抽中的幾率都增大。
如果直接把R跟獎(jiǎng)品各自的概率比較,根據(jù)我們上面的邏輯,它們總的中獎(jiǎng)空間H=2×100=200,只要R的值小于200,我們就已經(jīng)認(rèn)為中獎(jiǎng)了;但是當(dāng)a和b兩種獎(jiǎng)品的概率為99時(shí),只有當(dāng)R小于100時(shí)它們才會(huì)被抽中,R落在100到200之間將不被認(rèn)為中獎(jiǎng),這顯然是不對(duì)的。
搞清楚了上面的邏輯,剩下的就是處理一些特殊情況了。
比如,如果某些獎(jiǎng)品的概率為100,這就是我們之前說(shuō)的存在滿概率獎(jiǎng)品。按我們的設(shè)想,當(dāng)有百分百中獎(jiǎng)的獎(jiǎng)品時(shí),我們一定要這種獎(jiǎng)品被抽中。
處理這個(gè)問題,我的方法是把獎(jiǎng)品按概率分成兩組,一組是滿概率獎(jiǎng)品,一組是非滿概率獎(jiǎng)品。當(dāng)滿概率獎(jiǎng)品組不為空時(shí),從中隨機(jī)取出一個(gè)作為被抽中的獎(jiǎng)品放出,直到這些獎(jiǎng)品被抽完。
到此為止整個(gè)邏輯基本結(jié)束,可以著手寫代碼了。Good101!
/**
* 抽獎(jiǎng)核心算法
* @param prize array,所有概率不為0且剩余數(shù)大于0的獎(jiǎng)品數(shù)組
* @return array 單個(gè)獎(jiǎng)品
* version 2015.12.21
* author thinkmad@sina.com
*/
const FULL_CHANCE = 100;
function calcPrize($prize){
if(!$prize){
return false;
}
$arr_chance = array();//所有獎(jiǎng)品概率
$arr_delimiter = array();//中獎(jiǎng)區(qū)間分界數(shù)組
$full_chance_prize = $nofull_chance_prize = array();//劃分滿概率和非滿概率數(shù)組
$H = 0;//中獎(jiǎng)概率空間
//劃分滿概率和非滿概率獎(jiǎng)品
foreach($prize as $item){
if($item['prizeChance'] >= self::FULL_CHANCE) {
$full_chance_prize[] = $item;
}else{
$nofull_chance_prize[$item['prizeID']] = $item;
}
}
//存在滿概率獎(jiǎng)品,則隨機(jī)取出一個(gè)獎(jiǎng)品并返回
$len = count($full_chance_prize);
if($len > 0){
$r = mt_rand(0,$len-1);
return $full_chance_prize[$r];
}
//計(jì)算總概率空間O
$O = count($prize) * self::FULL_CHANCE;
//計(jì)算總中獎(jiǎng)空間H并生成概率數(shù)組
foreach($nofull_chance_prize as $k => $v){
$H += $v['prizeChance'];
$arr_chance[$k] = $v["prizeChance"];
}
$R = mt_rand(1,$O);
if($R > $H){ //R不在中獎(jiǎng)空間
return false;
}else{//R落在中獎(jiǎng)空間
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];
}
}
}
}
/**
* 輔助函數(shù)array_isum,計(jì)算數(shù)組中i起n個(gè)數(shù)的和
* @params $input array,要計(jì)算的數(shù)組
* @params $start,int,起始位置
* @params $num,int,個(gè)數(shù)
* @return int
*/
function array_isum($input,$start,$num){
$temp = array_slice($input, $start,$num);
return array_sum($temp);
}
上面是用PHP實(shí)現(xiàn)的一個(gè)核心算法,其中定義了一個(gè)常量FULL_CHANCE
為100。還定義了一個(gè)輔助函數(shù)array_isum
,用來(lái)計(jì)算一個(gè)數(shù)組中從下標(biāo)i開始n個(gè)數(shù)的和。
這里面其實(shí)還有一個(gè)小小的問題,就是當(dāng)幾種獎(jiǎng)品都是非滿概率獎(jiǎng)品且它們的概率相同,我們需要隨機(jī)抽出一個(gè)作為獎(jiǎng)品放出,如果不做這個(gè)處理,則會(huì)按照默認(rèn)順序先把前面的獎(jiǎng)品發(fā)完再發(fā)后面的。
Enjoy It!