摘要:京東券電影票淘寶券代碼如下按照概率抽取一個(gè)獎(jiǎng)品返回獎(jiǎng)品所有獎(jiǎng)品的概率總和應(yīng)該為總概率基數(shù)方式二該方式如果直接看代碼比較難理解。
首發(fā)于 樊浩柏科學(xué)院
需求:首先用戶通過以一定方式(好友點(diǎn)贊等)開啟抽獎(jiǎng)資格,然后按照用戶 100% 中獎(jiǎng)概率進(jìn)行抽獎(jiǎng),且系統(tǒng)的發(fā)放獎(jiǎng)品需要按照各個(gè)獎(jiǎng)品整體的期望中獎(jiǎng)比例來進(jìn)行分布,最后用戶抽中獎(jiǎng)品調(diào)用第三方發(fā)放接口發(fā)放獎(jiǎng)品并記錄保存,另有些獎(jiǎng)品存在發(fā)放數(shù)量限制。
問題分析整個(gè)抽獎(jiǎng)過程是同步進(jìn)行,由于前置了開啟抽獎(jiǎng)資格保護(hù),會(huì)避免用戶集中進(jìn)行抽獎(jiǎng),故系統(tǒng)并發(fā)量并不會(huì)太高。突出的問題主要有以下幾個(gè):
1)由于同步調(diào)用第三方接口發(fā)放獎(jiǎng)品,獎(jiǎng)品可能發(fā)放失敗;
2)有一些獎(jiǎng)品存在數(shù)量限制,可能已經(jīng)發(fā)放完;
3)系統(tǒng)要求用戶 100% 抽中獎(jiǎng)品;
4)系統(tǒng)要求各個(gè)獎(jiǎng)品總的發(fā)放情況符合預(yù)期的比例分布;
針對以上突出問題,給出針對的解決辦法。
問題1:采用帶有次數(shù)限制的重試機(jī)制,降低獎(jiǎng)品發(fā)放接口發(fā)放失敗情況,同時(shí)捕獲異常來應(yīng)對接口返回異常信息。重試機(jī)制失敗則自動(dòng)重新進(jìn)行一輪按概率抽獎(jiǎng),依次類推并做重發(fā)次數(shù)限制;
問題2:獎(jiǎng)品數(shù)量在獎(jiǎng)品發(fā)放端進(jìn)行限制。因?yàn)橄到y(tǒng)存在數(shù)量限制的獎(jiǎng)品期望發(fā)放比例較低,每輪抽中這些獎(jiǎng)品概率也較低,所以可以采用若獎(jiǎng)品已發(fā)放完,則自動(dòng)重新進(jìn)行一輪按概率抽獎(jiǎng),依次類推并做重發(fā)次數(shù)限制;
問題3:盡管有發(fā)放接口的重試機(jī)制和自動(dòng)多輪按概率抽獎(jiǎng)機(jī)制,也可能存在抽取獎(jiǎng)品失敗的情況,這里采用一種特定獎(jiǎng)品作為兜底的辦法,當(dāng)然兜底獎(jiǎng)品也有重試機(jī)制,使用戶抽中概率接近 100%;
問題4:因?yàn)橹卦嚈C(jī)制失敗或者抽取到已經(jīng)發(fā)送完畢的獎(jiǎng)品時(shí),會(huì)自動(dòng)重新進(jìn)行下一輪抽獎(jiǎng),由于規(guī)則也是按照概率抽獎(jiǎng),所以不影響各個(gè)獎(jiǎng)品總的比例分布情況;
編碼 按概率抽獎(jiǎng)核心思想是采用隨機(jī)函數(shù) mt_rand() 來模擬用戶抽獎(jiǎng)。
獎(jiǎng)品信息如下:
//所有獎(jiǎng)品信息 $allPrizes = [ "jd" => ["name" => "京東券", "probability" => 30], "film" => ["name" => "電影票", "probability" => 10], "tb" => ["name" => "淘寶券", "probability" => 60], ]
方式一
這是一個(gè)比較中規(guī)中矩的方式,主要思想 是:將所有獎(jiǎng)品按照期望比例分布,一段一段小區(qū)間分布到 1~100 這個(gè)區(qū)間,然后隨機(jī)一個(gè) 1~100 的隨機(jī)數(shù),如果這個(gè)隨機(jī)數(shù)落在某段區(qū)間,則表示抽取對應(yīng)區(qū)間的獎(jiǎng)品。
1 30 10 60 1|-----------|------|----------------------|100 京東券 電影票 淘寶券
代碼如下:
/** * 按照概率抽取一個(gè)獎(jiǎng)品, 返回獎(jiǎng)品 * @param array $prizes 所有獎(jiǎng)品的probability概率總和應(yīng)該為100 * @return mixed */ private function randPrize(array $prizes) { //總概率基數(shù) $totalProbability = array_sum(array_column(array_values($prizes), "probability")); if (100 !== $totalProbability) { throw new Exception("invalid probability config"); } $rand = mt_rand(1, 100); $cursor = 0; $id = ""; while(list($key, $item) = each($prizes)) { if ($rand > $cursor && $rand <= $cursor + $item["probability"]) { $id = $key; break; } $cursor += $item["probability"]; } unset($prizes[$id]["probability"]); return $prizes[$id] + ["id" => $id]; }
方式二
該方式如果直接看代碼比較難理解。主要思想:按照給定順序(按照獎(jiǎng)品配置順序),先后一個(gè)一個(gè)抽取獎(jiǎng)品,直到抽中一個(gè)獎(jiǎng)品為止, 抽中后續(xù)獎(jiǎng)品的概率的前提是沒有抽中當(dāng)前獎(jiǎng)品,多次抽取概率應(yīng)該相乘。
例如:
次數(shù) 獎(jiǎng)品 概率 基數(shù) 中獎(jiǎng)概率 未中獎(jiǎng)概率 1 京東券 30 100 30/100 70/100 2 電影票 10 70 (70/100)*(10/70) (70/100)*(60/70) 3 淘寶券 60 60 (70/100)*(60/70)*(1) 1-(70/100)*(60/70)*(1)
/** * 按照概率抽取一個(gè)獎(jiǎng)品, 返回獎(jiǎng)品, * @param array $prizes 參與抽獎(jiǎng)的獎(jiǎng)品信息, 所有獎(jiǎng)品的probability概率總和應(yīng)該為100 * @return array */ private function randPrize(array $prizes) { //總概率基數(shù) $totalProbability = array_sum(array_column(array_values($prizes), "probability")); if (100 !== $totalProbability) { throw new Exception("invalid probability config"); } //可以考慮按照概率倒序排序 /*uasort($prizes, function(array $a, array $b) { if ($a["probability"] == $b["probability"]) return 0; return $a["probability"] > $b["probability"] ? -1 : 1; });*/ //按照獎(jiǎng)品順序依次模擬抽中獎(jiǎng)品 $id = ""; foreach ($prizes as $key => $item) { $rand = mt_rand(1, $totalProbability); //本次抽獎(jiǎng)的基數(shù) if ($rand <= $item["probability"]) { //表示抽中 $id = $key; break; } else { $totalProbability -= $item["probability"]; //后續(xù)獎(jiǎng)品基數(shù)減去抽過的概率, 因?yàn)槌橹泻笠粋€(gè)獎(jiǎng)品的前提是抽不中前一些獎(jiǎng)品 } } unset($prizes[$id]["probability"]); return $prizes[$id] + ["id" => $id]; }抽中獎(jiǎng)品
主要包含重試機(jī)制、自動(dòng)重新一輪按照概率抽獎(jiǎng)機(jī)制、兜底機(jī)制的實(shí)現(xiàn)。
/** * 抽獎(jiǎng) * @param array $allPrizes * @return mixed */ public function draw($allPrizes) { $tryTimes = 0; $outPrize = []; $prize = []; //如果抽到有數(shù)量限制獎(jiǎng)品且獎(jiǎng)品也已經(jīng)抽完或者抽取失敗, 最多抽獎(jiǎng)次數(shù) while ($tryTimes < 4) { $tryTimes++; //按照概率抽取 $prize = $this->randPrize($allPrizes); //模擬發(fā)放獎(jiǎng)品方法 $outPrize = $this->getOnePrize($prize["id"]); //抽中退出 if (!empty($outPrize)) { break; } } echo "嘗試按照概率抽取次數(shù):" , $tryTimes, PHP_EOL; //多次抽獎(jiǎng)都抽中已經(jīng)抽完的獎(jiǎng)品, 則用兜底獎(jiǎng)品兜底 $tryTimes = 0; while (!$outPrize && $tryTimes < 2) { $tryTimes++; $prize = $allPrizes["default"] + ["id" => "default"]; $outPrize = $this->getOnePrize("default"); } echo "兜底抽取次數(shù):" , $tryTimes, PHP_EOL; if (!$outPrize) { //兜底失敗, 可能是券達(dá)到上限, 或者接口down了 return false; } else { //合并獎(jiǎng)品信息 $outPrize = $outPrize + $prize; } return $outPrize; }驗(yàn)證 概率分布
抽樣方法
public function sample($all, $times) { $out = []; $count = $times; if ($times > 1000000) return; while ($times) { $times--; $prize = $this->draw($all); if (!isset($out[$prize["id"]])) { $out[$prize["id"]] = 0; } $out[$prize["id"]]++; } array_walk($out, function(&$value, $key) use ($count) { $value = ($value / $count * 100); }); ksort($out); return $out; }
抽樣結(jié)果
//期望概率 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" }異常處理機(jī)制
嘗試按照概率抽取次數(shù): 3 兜底抽取次數(shù): 0 抽中獎(jiǎng)品為:array(3) { ["name"] => string(20) "淘寶50元消費(fèi)券" ["content"] => string(12) "WD84-3233-21" ["id"] => string(2) "tb" }
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/29764.html
摘要:在開放時(shí)間的基礎(chǔ)上加上類型概率這種方式,也會(huì)出現(xiàn)多個(gè)用戶相同獎(jiǎng)品,但加上限制后,用戶被分散在各個(gè)類型中,未中獎(jiǎng)概率會(huì)比上面的例子低。 本文講解內(nèi)容 針對兩類發(fā)獎(jiǎng)需求的四種抽獎(jiǎng)邏輯及細(xì)節(jié) 一般H5抽獎(jiǎng)活動(dòng)的發(fā)獎(jiǎng)需求分為 1.一定中獎(jiǎng)(獎(jiǎng)品庫存不空的情況下)2.不一定中獎(jiǎng) 發(fā)獎(jiǎng)接口的最終實(shí)現(xiàn)要求 1.獎(jiǎng)品不超發(fā)2.唯一獎(jiǎng)品單次發(fā)放3.對并發(fā)有一定的限制 接口實(shí)戰(zhàn) 1.根據(jù)獎(jiǎng)品開放時(shí)間進(jìn)行抽...
摘要:實(shí)現(xiàn)功能抽獎(jiǎng)需求后臺控制每種中獎(jiǎng)的概率控制獎(jiǎng)品數(shù)量備注獎(jiǎng)品是以優(yōu)惠券的形式發(fā)放給用戶進(jìn)行兌換,需要考慮到優(yōu)惠券數(shù)量問題以此為例九宮格一個(gè)開始,一個(gè)謝謝參與,后臺設(shè)置七個(gè)獎(jiǎng)品獲取每個(gè)獎(jiǎng)品中獎(jiǎng)概率,及該獎(jiǎng)品的我是以的總數(shù)概率來計(jì)算的獲取獎(jiǎng)品及該 實(shí)現(xiàn)功能:抽獎(jiǎng) 需求:后臺控制每種中獎(jiǎng)的概率、控制獎(jiǎng)品數(shù)量 備注:獎(jiǎng)品是以優(yōu)惠券的形式發(fā)放給用戶進(jìn)行兌換,需要考慮到優(yōu)惠券數(shù)量問題 showImg...
摘要:演示下載地址效果圖三個(gè)金蛋一把錘子及中獎(jiǎng)結(jié)果代碼如下錘子當(dāng)鼠標(biāo)滑向金蛋時(shí),錘子會(huì)僅靠金蛋右上方,通過來控制位置。當(dāng)揮動(dòng)錘子砸向金蛋前,我們先把金蛋中的數(shù)字編號隱藏起來。最后,我們向后臺發(fā)送一個(gè)請求,后臺程序會(huì)處理獎(jiǎng)項(xiàng)分配并把中獎(jiǎng)結(jié)果返回。 演示下載地址:http://www.erdangjiade.com/js...效果圖:showImg(https://segmentfault.co...
摘要:演示下載地址效果圖三個(gè)金蛋一把錘子及中獎(jiǎng)結(jié)果代碼如下錘子當(dāng)鼠標(biāo)滑向金蛋時(shí),錘子會(huì)僅靠金蛋右上方,通過來控制位置。當(dāng)揮動(dòng)錘子砸向金蛋前,我們先把金蛋中的數(shù)字編號隱藏起來。最后,我們向后臺發(fā)送一個(gè)請求,后臺程序會(huì)處理獎(jiǎng)項(xiàng)分配并把中獎(jiǎng)結(jié)果返回。 演示下載地址:http://www.erdangjiade.com/js...效果圖:showImg(https://segmentfault.co...
閱讀 674·2021-11-15 11:37
閱讀 4129·2021-09-09 09:34
閱讀 3571·2019-08-30 15:52
閱讀 2609·2019-08-29 14:03
閱讀 2851·2019-08-26 13:36
閱讀 1592·2019-08-26 12:16
閱讀 1600·2019-08-26 11:45
閱讀 3492·2019-08-23 18:41