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