有幸被问到了红包算法,当时一时没有想到更好的解决方案,现在花了一点时间研究了一下,下面做详细说明。
先上代码:
$total = 10; //总金额
$min = 0.01; //最小红包
$count = 10; //总红包
$balance = $total; //剩余红包
$data = [];
for ($i = 1; $i <= $count; $i++) {
if ($i !== $count) {
$indexMoney = rand($min * 100, ($balance - ($count - $i) * $min) * 100) / 100;
$data[] = $indexMoney;
$balance = $balance - $indexMoney;
} else {
$data[] = $balance;
}
}
$sum = array_sum($data);
var_dump($data);
var_dump($sum);
说明:
1、假设我们有10元钱,准备要发10个红包且10个红包必须全部发完
刚开始发钱时,我们将总金额total赋值给剩余金额balance:因为刚开始剩余金额就是总金额
2、那么第一个人领的钱的范围为0.01到剩余金额减去剩下的人数×最小金额(要保证所有人都有钱)【0.01 到 10 -(10-1)* 0.01】
3、第二个人发钱的范围是【0.01 到 balance -(10 - 2)* 0.01】
4、那么第N个人为【0.01 到 balance -(总红包数 - N)* 0.01】
因为这里是按照分来算,所以我们将红包先扩大100倍,也是便于使用rand函数来做,然后我们再除100就保证了当前的金额为分
$indexMoney = rand($min * 100, ($balance - ($count - $i) * $min) * 100) / 100
我们将结果放入一个数组中存下来,同时去更新我们的剩余金额
$data[] = $indexMoney;
$balance = $balance - $indexMoney;
当发现红包循环到最后一个时,那么剩余的金额就是最后一个红包的金额,就走的时else部分
当然最后为了验证我们的结果正确与否,我们使用了array_sum来累加
其实这个算法还可以进行补充不够完美,在实际中,我们可能会遇到浮点数的减法丢失精度的问题,所以我们最好加上bcmath扩展来做科学计算,可以使用bcadd和bcsub等来做加减,然后也可以使用php洗牌函数将数组乱序,这样就生成了一个乱序的随机的数组,然后将这个数据返回给上层,保证了每个红包至少都有0.01元且所有红包加起来为我们定义好的总金额。
下面我们看一组结果数据:
//生成的10个红包
0 => float 1.98
1 => float 1.38
2 => float 2.96
3 => float 2.3
4 => float 0.27
5 => float 0.31
6 => float 0.67
7 => float 0.08
8 => float 0.03
9 => float 0.02
//总金额为10元