当前位置: 首页 > 后端技术 > PHP

根据奖品概率分布抽奖的实现

时间:2023-03-29 19:31:25 PHP

首发于范浩博科学院需求:首先用户通过某种方式(好友点赞等)开启抽奖资格,然后按照用户100%的中奖概率进行抽奖,系统发放的奖品需要按照每个奖品的预期中奖比例进行分配。最后,用户调用第三方分发接口分发奖品,并保存中奖记录。有些奖品有分配次数限制。问题分析整个开奖过程是同步进行的。由于提前开启了抽奖资格保护,可以避免用户集中精力抽奖,所以系统并发度不会太高。存在的突出问题主要有:1)由于同步调用第三方接口发放奖品,可能导致奖品发放失败;2)部分奖品数量有限,可能已经发放完;3)系统要求用户中奖100%;4)系统要求各奖品的总分配符合预期的比例分配;该解决方案针对上述突出问题提供了具体的解决方案。问题一:使用有次数限制的重试机制来减少奖品发放接口的失败,捕获异常来处理接口返回的异常信息。若重试机制失败,将自动进行新一轮抽奖,依此类推,并限制重发次数;问题2:奖品发放端有奖品数量限制。由于系统的奖品数量有限,预期分配比例较低,每一轮中这些奖品的概率也较低。因此,若奖品已发放,将自动进行新一轮抽奖,依此类推,并计算补发次数。限制;问题三:虽然有发放接口的重试机制和基于概率的自动多轮抽奖机制,但可能会出现抽不到奖品的情况。这里,使用特定的奖品作为自下而上的方法。当然,自下而上的奖品也有重试机制。使用户中奖概率接近100%;问题四:当重试机制失效或已经发出的奖品被抽出时,会自动重新进行下一轮抽奖。由于规则也是按照概率抽取的,所以不影响每次奖品的总金额。按比例分配;根据抽奖概率编码核心思想是利用随机函数mt_rand()来模拟用户抽奖。奖品信息如下://所有奖品信息$allPrizes=['jd'=>['name'=>'京东优惠券','概率'=>30],'film'=>['name'=>'movieTicket','probability'=>10],'tb'=>['name'=>'TaobaoCoupon','probability'=>60],]方法1这是比较标准的方法,主要思路是:将所有的奖品按照预期的比例分配,小范围内分配到1~100的范围内,然后从1到100之间随机抽取一个数,如果这个随机数落在某个范围内,则表示将抽取相应范围内的奖品。13010601|------------|------|--------------------|100张京东券电影票淘宝优惠券代码如下:/***根据概率抽取奖品,并返还奖品*@paramarray$prizes所有奖品的概率probabilities之和应为100*@returnmixed*/privatefunctionrandPrize(array$prizes){/$totalProbability=array_sum(array_column(array_values($prizes),'probability'));if(100!==$totalProbability){thrownewException('无效概率配置');}$rand=mt_rand(1,100);$游标=0;$id='';while(list($key,$item)=each($prizes)){if($rand>$cursor&&$rand<=$cursor+$item['probability']){$id=$key;休息;}$cursor+=$item['概率'];}unset($prizes[$id]['probability']);return$prizes[$id]+['id'=>$id];}方法二这种方法直接看代码比较难理解。主要思路:按照给定的顺序(按照奖品配置顺序),逐个抽奖,直到抽到一个奖品。后续奖品中奖概率的前提是当前奖品没有抽到,多次抽奖的概率要相乘。例如:中奖次数基础中奖概率未中奖概率1京东优惠券3010030/10070/1002电影票1070(70/100)*(10/70)(70/100)*(60/70)3淘宝优惠券6060(70/100)*(60/70)*(1)1-(70/100)*(60/70)*(1)/***按概率抽奖,返回奖品,*@paramarray$prizes参与抽奖的中奖信息,所有奖品的概率概率之和应该为100*@returnarray*/privatefunctionrandPrize(array$prizes){//总概率base$totalProbability=array_sum(array_column(array_values($prizes),'probability'));if(100!==$totalProbability){thrownewException('无效概率配置');}//可以考虑按概率倒序排序/*uasort($prizes,function(array$a,array$b){if($a['probability']==$b['probability'])return0;return$a['probability']>$b['probability']?-1:1;});*///模拟中奖顺序$id='';foreach($prizesas$key=>$item){$rand=mt_rand(1,$总概率);//本次抽奖的基数if($rand<=$item['probability']){//表示本次抽奖中奖$id=$key;休息;}else{$totalProbability-=$item['probability''];//后续奖品基数减去抽奖概率,因为抽取下一次奖品的前提是不能抽到之前的奖品}}unset($prizes[$id]['probability']);return$prizes[$id]+['id'=>$id];}中奖奖品主要包括重试机制,根据概率抽奖机制自动重轮,bottom-out机制的实现/***抽奖*@paramarray$allPrizes*@returnmixed*/publicfunctiondraw($allPrizes){$tryTimes=0;$outPrize=[];$奖=[];//如果有抽奖次数限制奖品和奖品已经抽到或失败,则最大抽奖次数while($tryTimes<4){$tryTimes++;//根据概率抽取$prize=$this->randPrize($allPrizes);//模拟发奖方法$outPrize=$this->getOnePrize($prize['id']);//如果(!empty($outPrize)){break;}}echo'尝试按概率绘制:',$tryTimes,PHP_EOL;//如果中奖的是多次抽奖的奖品,则使用底奖$tryTimes=0;while(!$outPrize&&$tryTimes<2){$tryTimes++;$prize=$allPrizes['default']+['id'=>'default'];$outPrize=$this->getOnePrize('default');}echo'自下而上绘制的次数:',$tryTimes,PHP_EOL;if(!$outPrize){//BottomFailed,可能是优惠券已经达到上限,或者界面down了returnfalse;}else{//合并奖品信息$outPrize=$outPrize+$prize;}return$outPrize;}验证概率分布抽样方法publicfunctionsample($all,$times){$出=[];$计数=$次;如果($次>1000000)返回;while($times){$times--;$prize=$this->draw($all);如果(!isset($out[$prize['id']])){$out[$prize['id']]=0;$out[$prize['id']]++;}array_walk($out,function(&$value,$key)使用($count){$value=($value/$count*100);});ksort($输出);return$out;}采样结果//期望概率array(3){["film"]=>int(10)["jd"]=>int(30)["tb"]=>int(60)}//采样2000次array(3){["film"]=>string(4)"9.8"["jd"]=>string(6)"31.35"["tb"]=>string(6)"58.85"}异常处理机制尝试抽奖概率:3次数:0抽奖奖品为:array(3){["name"]=>string(20)"淘宝50元券"["content"]=>字符串(12)"WD84-3233-21"["id"]=>字符串(2)"tb"}