本文由腾讯梁中原分享,原题“红包算法揭秘!哪段代码让你只抢了0.01元?”,即时通讯网进行了排版和内容优化等。
cover-opti.png (11.05 KB, 下载次数: 308)
下载附件 保存到相册
6 个月前 上传
// 剩余值随机 ,优点:逻辑简单,缺点:随机区间步步减少,可以明显看出随机值的递减特性, 对后面玩家极不公平,且容易被抓到规律,造成舆论不满。 // 做抢红包体验很差,稍微弥补一点的方案:shuffle一下随机数组,让看起来不那么递减明显。 $res = LeftMoneyRedbag($Moneys, $userNums, $isEveryHave); //余值随机红包算法 ,一般都是使用剩余值在计算一把。 function LeftMoneyRedbag($Moneys, $userNums, $isEveryHave = 1, $baseMoney = 1) { if ($Moneys <= 0 || $userNums <= 0) { return ['code' => -3, 'msg' => '红包金额或拆红包总人数不合法']; } if ($isEveryHave && $userNums > $Moneys) { return ['code' => -4, 'msg' => '红包数量不足']; } //是否每个人都必有 if ($isEveryHave) { $Moneys = $Moneys - ($userNums * $baseMoney); //此时剩余money可能会无法随机到每一个人 } $userMoney = []; //正式执行余值随机 $leftMoneys = $Moneys; //可能 50分钱 分100人 $leftUserNums = $userNums; while ($leftUserNums > 1) { // 考虑:就一个用户瓜分 // echo "leftMoneys = " . $leftMoneys . " , leftUserNums = " . $leftUserNums . "<br>"; $RandVal = 0; if ($leftMoneys > 0) { //考虑:剩余的钱不够分 $RandVal = mt_rand(0, $leftMoneys); $leftMoneys = $leftMoneys - $RandVal; } $userMoney[] = $isEveryHave ? ($baseMoney + $RandVal) : $RandVal; $leftUserNums--; } //最后一位。考虑:剩余的钱太多或者就一个人 $userMoney[] = $isEveryHave ? ($baseMoney + $leftMoneys) : $leftMoneys; echo "总数:" . count($userMoney) . "<br>"; var_dump($userMoney); echo "总值:" . array_sum($userMoney) . "<br>"; return ['code' => 0, 'msg' => "success", 'redbag' => $userMoney]; }
$Moneys = 99 * 10; //单位为分 $userNums = 990; $isEveryHave = 0; //是否每个人都有 $res = doubleMeanRedbag($Moneys, $userNums); // var_dump($res); //二倍均值算法 function doubleMeanRedbag($Moneys, $userNums, $isEveryHave = 1, $baseMoney = 1) { if ($Moneys <= 0 || $userNums <= 0) { return ['code' => -3, 'msg' => '红包金额或拆红包总人数不合法']; } if ($isEveryHave && $userNums > $Moneys) { return ['code' => -4, 'msg' => '红包数量不足']; } //是否每个人都必有 if ($isEveryHave) { $Moneys = $Moneys - ($userNums * $baseMoney); //此时money可能会无法随机到每一个人 } $userMoney = []; //正式执行二倍均值 $leftMoneys = $Moneys; //可能 50分钱 分100人 $leftUserNums = $userNums; while ($leftUserNums > 1) { // 考虑:就一个用户瓜分 // echo "leftMoneys = " . $leftMoneys . " , leftUserNums = " . $leftUserNums . "<br>"; $RandVal = 0; if ($leftMoneys > 0) { //考虑:剩余的钱不够分 $doubleMeans = ceil($leftMoneys / $leftUserNums * 2); $RandVal = mt_rand(0, $doubleMeans); $leftMoneys = $leftMoneys - $RandVal; } $userMoney[] = $isEveryHave ? ($baseMoney + $RandVal) : $RandVal; $leftUserNums--; } //最后一位。考虑:剩余的钱太多 $userMoney[] = $isEveryHave ? ($baseMoney + $leftMoneys) : $leftMoneys; // echo "总数:" . count($userMoney) . "<br>"; // var_dump($userMoney); // echo "总值:" . array_sum($userMoney) . "<br>"; return ['code' => 0, 'msg' => "success", 'redbag' => $userMoney]; }
//线段分割算法 -- 有个致命缺陷,随机值碰撞,分割数量越接近总金额,碰撞概率越大 ,所以最好 userNum数量与总金额差的越大越好 function lineSegmentRedbag($Moneys, $userNums, $isEveryHave = 1, $baseMoney = 1) { if ($Moneys <= 0 || $userNums <= 0) { return ['code' => -3, 'msg' => '红包金额或拆红包总人数不合法']; } if ($isEveryHave && $userNums > $Moneys) { return ['code' => -4, 'msg' => '红包数量不足']; } $cutPoints = []; //切割点数组 $pointNums = $userNums - 1; //存放的 $userMoney = []; //每一个用户该分得的钱 //正式线段分割,完全随机 // $j = 0; // 当 用户数 和 总金额差距不大时,这种写法效率极差 while ($pointNums > 0) { if ($isEveryHave == 1) { $randVal = mt_rand(1, $Moneys - 1); //每个人都有,mt_rand包含区间边界的,即包含最大值 和 最小值 ,1和2都会出现 } else { $randVal = mt_rand(0, $Moneys); //所有用户,全区间随机,保证了公平,所有人概率一致 0~10。如果$Moneys设置-1,导致最后一位必定不为0 } if (in_array($randVal, $cutPoints)) { //这边会产生随机碰撞,500个随机需要2500次左右才能覆盖。 // $j++; continue; } $cutPoints[] = $randVal; $pointNums--; } // echo "无效循环次数:" . $j . "<br>"; // echo "最终切割点数组数量:" . count($cutPoints) . "<br>"; // var_dump($cutPoints); // return; //根据cutPoint计算每个人所得 同时考虑:就一个人 $lastVal = 0; if (count($cutPoints) > 0) { sort($cutPoints); foreach ($cutPoints as $RandPoint) { $userMoney[] = $RandPoint - $lastVal; $lastVal = $RandPoint; } } $lastDiff = $Moneys - $lastVal; $userMoney[] = $lastDiff; // echo "总数:" . count($userMoney) . "<br>"; // echo "总值:" . array_sum($userMoney) . "<br>"; return ['code' => 0, 'msg' => "success", 'redbag' => $userMoney]; }
//利用array_rand一次拿出多个随机值时,随机且去重,且随机区间包括首尾。 function lineSegmentOptimize($Moneys, $userNums, $isEveryHave = 1) //$baseMoney = 1默认为1 { if ($Moneys <= 0 || $userNums <= 0) { return ['code' => -3, 'msg' => '红包金额或拆红包总人数不合法']; } if ($isEveryHave && $userNums > $Moneys) { return ['code' => -4, 'msg' => '红包数量不足']; } $cutPoints = []; $userMoney = []; if ($isEveryHave) { $MoneysArr = array_fill(1, $Moneys - 1, 0); //转成数组时,去掉头尾得-1,如果10,则下标是1-9 } else { $MoneysArr = array_fill(0, $Moneys + 1, 0); //转成数组,为了保留头尾得+1,如果10,则下标是0-10,array_rand区间包含首尾 } if ($userNums == 1) { $userMoney[] = $Moneys; return ['code' => 0, 'msg' => "success", 'redbag' => $userMoney]; } $cutPoints = array_rand($MoneysArr, $userNums - 1); //多随机、且去重、且区间包含首尾,array_rand第二个值不可为0 sort($cutPoints); $lastVal = 0; foreach ($cutPoints as $randPoint) { $diff = $randPoint - $lastVal; $userMoney[] = $diff; $lastVal = $randPoint; } $lastDiff = $Moneys - $lastVal; $userMoney[] = $lastDiff; // echo "总数:" . count($userMoney) . "<br>"; // var_dump($userMoney); // echo "总值:" . array_sum($userMoney) . "<br>"; return ['code' => 0, 'msg' => "success", 'redbag' => $userMoney]; }
$res = checkRand(10, 10000); var_dump($res); function checkRand($range, $num) { $statiArr = array_fill(0, 100, 0); $sourceArr = range(0, 99); for ($i = 0; $i < 10000; $i++) { $indexArr = array_rand($sourceArr, 4); //array_rand随机性可以,且去重性也可以 foreach ($indexArr as $index) { //中途也用array_unique统计,是否单把拿值重复 $statiArr[$index]++; } } return $statiArr; }
array(100) { [0]=> int(196) [1]=> int(210) [2]=> int(206) [3]=> int(202) ,,,,[97]=> int(196) [98]=> int(197) [99]=> int(188) }
array(100) { [0]=> int(372) [1]=> int(428) [2]=> int(394) [3]=> int(441) ,,,,, [97]=> int(382) [98]=> int(388) [99]=> int(358) }
array(100) { [0]=> int(9892) [1]=> int(9890) [2]=> int(9913) [3]=> int(9909) ,,,,[97]=> int(9908) [98]=> int(9903) [99]=> int(9908) }
function microTime_float() { //$usec 精确到微秒 ,$sec 秒 1秒(second) = 1000毫秒(millisecond) = 1000,000微秒(microsecond) list($usec, $sec) = explode(' ', microtime()); return ((float)$usec + (float)$sec); //float保留小数点后四位 } $starTime = microTime_float(); //0.35529400 1616661516 for ($i = 0; $i < 100000; $i++) { lineSegmentRedbag($Moneys, $userNums, $isEveryHave); // lineSegmentOptimize($Moneys, $userNums, $isEveryHave); // doubleMeanRedbag($Moneys, $userNums, $isEveryHave); } $endTime = microTime_float(); $diff = floatval($endTime) - floatval($starTime); echo "线段分割时间差:" . floatval($diff) . "<br/>"; //时间差:0.33733010292053 //Optimize时间差:0.11269283294678 exit;
1.png (44.92 KB, 下载次数: 303)
2.png (20.01 KB, 下载次数: 267)
来源:即时通讯网 - 即时通讯开发者社区!
轻量级开源移动端即时通讯框架。
快速入门 / 性能 / 指南 / 提问
轻量级Web端即时通讯框架。
详细介绍 / 精编源码 / 手册教程
移动端实时音视频框架。
详细介绍 / 性能测试 / 安装体验
基于MobileIMSDK的移动IM系统。
详细介绍 / 产品截图 / 安装体验
一套产品级Web端IM系统。
详细介绍 / 产品截图 / 演示视频
引用此评论
引用:cqhigh 发表于 2024-06-03 13:40 还是没搞清楚为什么我抢的是0.01
精华主题数超过100个。
连续任职达2年以上的合格正式版主
为论区做出突出贡献的开发者、版主等。
Copyright © 2014-2024 即时通讯网 - 即时通讯开发者社区 / 版本 V4.4
苏州网际时代信息科技有限公司 (苏ICP备16005070号-1)
Processed in 0.122075 second(s), 40 queries , Gzip On.