2020-04-03 18:10:42
围观(3469)
很多 APP / 小程序 为了增强用户交互或者激励用户都会开发一套抽奖.
博主最近就遇到了这样的抽奖需求.
设置多个奖品 如:
A奖品 中奖概率百分之十 总量十个 B奖品 中奖概率百分之五 总量五个 C奖品 中奖概率百分之十五 总量十五个 D奖品 中奖概率百分之十 总量十个 F奖品 中奖概率百分之五 总量五个 G奖品 中奖概率百分之二十 总量二十个 H奖品 中奖概率百分之五 总量五个 I奖品 中奖概率百分之五 总量五个
将这些奖品信息写入数组:
$list_awards = [ ['id' => 1, 'name' => '奖品1', 'probability' => 10, 'count' => 10], ['id' => 2, 'name' => '奖品2', 'probability' => 5, 'count' => 5], ['id' => 3, 'name' => '奖品3', 'probability' => 15, 'count' => 15], ['id' => 4, 'name' => '奖品4', 'probability' => 10, 'count' => 10], ['id' => 5, 'name' => '奖品5', 'probability' => 5, 'count' => 5], ['id' => 6, 'name' => '奖品6', 'probability' => 20, 'count' => 20], ['id' => 7, 'name' => '奖品7', 'probability' => 5, 'count' => 5], ['id' => 8, 'name' => '奖品8', 'probability' => 5, 'count' => 5], ];
直接上代码:
$map_list_awards = []; foreach ($list_awards as $key => $value) { $map_list_awards[$value['id']] = $value; } // 存放奖品 $new_list_awards = []; foreach ($map_list_awards as $row_awards) { // 判断余量 / 总量 当余量大于等于一时 即奖池中有足够的库存才进行抽奖 if ($row_awards['count'] >= 1) { for ($i = 1; $i <= $row_awards['probability']; $i++) { // 按照概率存入数组 比如奖品中奖改成为五 则会入库五个 $new_list_awards[] = $row_awards; } } } // 计算当前剩余的百分比 $probability = 100 - array_sum(array_column($list_awards, 'probability')); // 将剩下的百分比 作为空数组存入 for ($i = 0; $i < $probability; $i++) { $new_list_awards[] = []; } // 生成随机数 因为数组是从索引 0 开始 所以长度一百的数组 索引只到 99 $select_index = mt_rand(0, 99); if (empty($new_list_awards[$select_index])) { // 如果是空数组 自然没中奖 echo "谢谢惠顾"; } else { // 存入文件 更加方便查看中奖记录, 也可以存入数据库 $file = fopen('test.log', 'a+'); fwrite($file, '奖品ID:' . $new_list_awards[$select_index]['id'] . '; 抽第' . $o . '次时中奖; 奖品几率' . $new_list_awards[$select_index]['probability'] . '; 奖品剩余:' . $new_list_awards[$select_index]['count'] . " "); fclose($file); echo '恭喜中奖了, 奖品:' . $new_list_awards[$select_index]['name'] . " "; // 中奖了当然要减少奖品余量 $map_list_awards[$new_list_awards[$select_index]['id']]['count']--; }
这个抽奖原理就是, 每个奖品设置中奖率, 全部奖品中奖率加起来是等于 100 (百分百), 根据这个特性, 创建了一个长度为 100 的数组, 比如设置的奖品总和几率是 70, 那么这个数组 1 到 70 的索引都有对应奖品的值, 然后在 1 到 100 之间生成随机数, 如果随机数是在 1 - 70 之间, 说明中奖了. 当然 中奖率还可以再调.
代码写好了, 马上进行测试, 使用 PHP CLI 执行文件测试, 测试条件: 抽奖十万次.
跑完后打开存储记录的文件, 显示信息如下:
奖品ID:3; 抽第0次时中奖; 奖品几率15; 奖品剩余:15 奖品ID:6; 抽第1次时中奖; 奖品几率20; 奖品剩余:20 奖品ID:6; 抽第2次时中奖; 奖品几率20; 奖品剩余:19 奖品ID:6; 抽第3次时中奖; 奖品几率20; 奖品剩余:18 奖品ID:1; 抽第4次时中奖; 奖品几率10; 奖品剩余:10 奖品ID:6; 抽第5次时中奖; 奖品几率20; 奖品剩余:17 奖品ID:4; 抽第6次时中奖; 奖品几率10; 奖品剩余:10 奖品ID:7; 抽第7次时中奖; 奖品几率5; 奖品剩余:5 奖品ID:5; 抽第8次时中奖; 奖品几率5; 奖品剩余:5 奖品ID:6; 抽第9次时中奖; 奖品几率20; 奖品剩余:16 奖品ID:6; 抽第10次时中奖; 奖品几率20; 奖品剩余:15 奖品ID:6; 抽第11次时中奖; 奖品几率20; 奖品剩余:14 奖品ID:2; 抽第13次时中奖; 奖品几率5; 奖品剩余:5 奖品ID:1; 抽第14次时中奖; 奖品几率10; 奖品剩余:9 奖品ID:3; 抽第16次时中奖; 奖品几率15; 奖品剩余:14 奖品ID:1; 抽第17次时中奖; 奖品几率10; 奖品剩余:8 奖品ID:4; 抽第19次时中奖; 奖品几率10; 奖品剩余:9 奖品ID:2; 抽第20次时中奖; 奖品几率5; 奖品剩余:4 奖品ID:6; 抽第21次时中奖; 奖品几率20; 奖品剩余:13 奖品ID:5; 抽第22次时中奖; 奖品几率5; 奖品剩余:4 奖品ID:6; 抽第23次时中奖; 奖品几率20; 奖品剩余:12 奖品ID:1; 抽第24次时中奖; 奖品几率10; 奖品剩余:7 奖品ID:7; 抽第25次时中奖; 奖品几率5; 奖品剩余:4 奖品ID:1; 抽第26次时中奖; 奖品几率10; 奖品剩余:6 奖品ID:3; 抽第28次时中奖; 奖品几率15; 奖品剩余:13 奖品ID:3; 抽第30次时中奖; 奖品几率15; 奖品剩余:12 奖品ID:4; 抽第31次时中奖; 奖品几率10; 奖品剩余:8 奖品ID:2; 抽第33次时中奖; 奖品几率5; 奖品剩余:3 奖品ID:6; 抽第35次时中奖; 奖品几率20; 奖品剩余:11 奖品ID:3; 抽第37次时中奖; 奖品几率15; 奖品剩余:11 奖品ID:3; 抽第39次时中奖; 奖品几率15; 奖品剩余:10 奖品ID:5; 抽第40次时中奖; 奖品几率5; 奖品剩余:3 奖品ID:1; 抽第41次时中奖; 奖品几率10; 奖品剩余:5 奖品ID:3; 抽第42次时中奖; 奖品几率15; 奖品剩余:9 奖品ID:3; 抽第44次时中奖; 奖品几率15; 奖品剩余:8 奖品ID:6; 抽第45次时中奖; 奖品几率20; 奖品剩余:10 奖品ID:6; 抽第46次时中奖; 奖品几率20; 奖品剩余:9 奖品ID:3; 抽第47次时中奖; 奖品几率15; 奖品剩余:7 奖品ID:4; 抽第49次时中奖; 奖品几率10; 奖品剩余:7 奖品ID:6; 抽第50次时中奖; 奖品几率20; 奖品剩余:8 奖品ID:3; 抽第51次时中奖; 奖品几率15; 奖品剩余:6 奖品ID:4; 抽第52次时中奖; 奖品几率10; 奖品剩余:6 奖品ID:7; 抽第54次时中奖; 奖品几率5; 奖品剩余:3 奖品ID:2; 抽第56次时中奖; 奖品几率5; 奖品剩余:2 奖品ID:2; 抽第58次时中奖; 奖品几率5; 奖品剩余:1 奖品ID:4; 抽第59次时中奖; 奖品几率10; 奖品剩余:5 奖品ID:4; 抽第60次时中奖; 奖品几率10; 奖品剩余:4 奖品ID:3; 抽第61次时中奖; 奖品几率15; 奖品剩余:5 奖品ID:6; 抽第62次时中奖; 奖品几率20; 奖品剩余:7 奖品ID:7; 抽第63次时中奖; 奖品几率5; 奖品剩余:2 奖品ID:6; 抽第64次时中奖; 奖品几率20; 奖品剩余:6 奖品ID:8; 抽第65次时中奖; 奖品几率5; 奖品剩余:5 奖品ID:6; 抽第66次时中奖; 奖品几率20; 奖品剩余:5 奖品ID:6; 抽第67次时中奖; 奖品几率20; 奖品剩余:4 奖品ID:7; 抽第68次时中奖; 奖品几率5; 奖品剩余:1 奖品ID:5; 抽第69次时中奖; 奖品几率5; 奖品剩余:2 奖品ID:3; 抽第71次时中奖; 奖品几率15; 奖品剩余:4 奖品ID:5; 抽第72次时中奖; 奖品几率5; 奖品剩余:1 奖品ID:1; 抽第73次时中奖; 奖品几率10; 奖品剩余:4 奖品ID:4; 抽第74次时中奖; 奖品几率10; 奖品剩余:3 奖品ID:3; 抽第75次时中奖; 奖品几率15; 奖品剩余:3 奖品ID:1; 抽第77次时中奖; 奖品几率10; 奖品剩余:3 奖品ID:3; 抽第78次时中奖; 奖品几率15; 奖品剩余:2 奖品ID:4; 抽第79次时中奖; 奖品几率10; 奖品剩余:2 奖品ID:1; 抽第81次时中奖; 奖品几率10; 奖品剩余:2 奖品ID:8; 抽第82次时中奖; 奖品几率5; 奖品剩余:4 奖品ID:6; 抽第83次时中奖; 奖品几率20; 奖品剩余:3 奖品ID:3; 抽第84次时中奖; 奖品几率15; 奖品剩余:1 奖品ID:4; 抽第86次时中奖; 奖品几率10; 奖品剩余:1 奖品ID:1; 抽第88次时中奖; 奖品几率10; 奖品剩余:1 奖品ID:6; 抽第92次时中奖; 奖品几率20; 奖品剩余:2 奖品ID:8; 抽第104次时中奖; 奖品几率5; 奖品剩余:3 奖品ID:6; 抽第113次时中奖; 奖品几率20; 奖品剩余:1 奖品ID:8; 抽第154次时中奖; 奖品几率5; 奖品剩余:2 奖品ID:8; 抽第158次时中奖; 奖品几率5; 奖品剩余:1
此时抽奖程序是正常的, 但是有个问题, 刚才测试抽的次数是 10W 次, 但是看记录 到158次 其实全部奖品都已经被抽完了. 第 158 次往后, 就只能全部都是谢谢惠顾了. 可以说是先到先得...
由于对后面进来的抽奖者来说并不公平, 解决方法就是, 将概率减小. 比如上面的代码存的数组长度只有 100 可以增大这个数组长度 就实现了减小记录.
如(直接贴完整代码 代码带循环十万次):
// 打开文件 准备好存储记录 因为是十万次抽奖测试 所以放在循环体外面 只打开一次 后面循环体内仅写入就好 $file = fopen('test.log', 'a+'); $list_awards = [ ['id' => 1, 'name' => '奖品1', 'probability' => 10, 'count' => 10], ['id' => 2, 'name' => '奖品2', 'probability' => 5, 'count' => 5], ['id' => 3, 'name' => '奖品3', 'probability' => 15, 'count' => 15], ['id' => 4, 'name' => '奖品4', 'probability' => 10, 'count' => 10], ['id' => 5, 'name' => '奖品5', 'probability' => 5, 'count' => 5], ['id' => 6, 'name' => '奖品6', 'probability' => 20, 'count' => 20], ['id' => 7, 'name' => '奖品7', 'probability' => 5, 'count' => 5], ['id' => 8, 'name' => '奖品8', 'probability' => 5, 'count' => 5], ]; // Map 处理 $map_list_awards = []; foreach ($list_awards as $key => $value) { $map_list_awards[$value['id']] = $value; } for ($o = 1; $o <= 100000; $o++) { $new_list_awards = []; // 将奖品项全部放入新数组 foreach ($map_list_awards as $row_awards) { if ($row_awards['count'] >= 1) { for ($i = 1; $i <= $row_awards['probability']; $i++) { $new_list_awards[] = $row_awards; } } } // 这里原本几率是 100 现在乘以 1000 减少几率 让后面的人也能抽奖 当然 中奖就变难了 $probability = (100 * 1000) - array_sum(array_column($list_awards, 'probability')); // 存入空数组 只要随机抽到空数组 说明没中奖 for ($i = 0; $i < $probability; $i++) { $new_list_awards[] = []; } $select_index = mt_rand(0, 99999); // 生成随机数 中奖就看这个了 原本仅仅是 99 减少了中奖几率 if (empty($new_list_awards[$select_index])) { echo '谢谢惠顾'; } else { // 减剩余奖品数量 $map_list_awards[$new_list_awards[$select_index]['id']]['count'] = $map_list_awards[$new_list_awards[$select_index]['id']]['count'] - 1; fwrite($file, '奖品ID:' . $new_list_awards[$select_index]['id'] . '; 抽第' . $o . '次时中奖; 奖品几率' . $new_list_awards[$select_index]['probability'] . '; 奖品剩余:' . $new_list_awards[$select_index]['count'] . " "); echo '中奖了:' . $new_list_awards[$select_index]['name']; } echo '第' . $o . "次 "; } fclose($file);
中奖几率重点就在这两句:
$probability = (100 * 1000) - array_sum(array_column($list_awards, 'probability')); $select_index = mt_rand(0, 99999);
当然 这个数字越大, 抽奖所需的服务器性能要求也会提高. 一般建议 设置成 100 * 100 然后索引值最大就是 9999.
以这个几率配置博主跑了两次抽奖十万次的, 第一次是第 14252 次就把所有奖品抽完了, 第二次是第 14484 次就把全部奖品抽完了. 两次都是 1.4W 次左右, 说明还是比较稳的, 而且这一共才 75 个奖品(八个奖项的数量加起来), 如果一人抽一次 需要 1.5W 人才能把这奖品抽完, 中奖几率控制在这个数值还是很难中奖的.
实际项目中的效果:
本文地址 : bubaijun.com/page.php?id=171
版权声明 : 未经允许禁止转载!
上一篇文章: 商城或外卖购物车功能开发
下一篇文章: PHP处理字符串隐私保护