最近碰到一些情況,把思路重新整理了一下,敲出代碼。記下來,以后可以借鑒,進一步優化等。
大致的思路:紅包主要分兩種,一種是平均分配,一種是隨機分配。
1、平均分配
平均分配相對好理解,只要把錢平均分給每一個人就可以了
這里有一個情況,就是錢的總額是固定的,但是分配的人數,不一定可以整除余0,那么剩下的如何分配呢?
這里,剩余的錢(極少),多分到的人,也就是多分1分錢(在計算處理時,單位是“分”)
所以,使用的處理辦法是,從前到后(誰手快,誰多分,蚊子再小也是肉),逐一分這剩余的錢,每人1分錢,直到錢沒為止
2、隨機分配
我這個隨機分配,比較簡單,只比平均分配多了一個步驟(此步驟根據需要,可以循環多次使用)。
先是隨機分配的兩個特殊情況:
a.總金額不夠所有人分。例如,最小的錢是1分錢,分給10個人。是不可以的
b.總金額正好只能每人平均分1分錢。例如,0.1元,分給10個人,任何一個人多1分錢,就會有人沒錢分
這兩個特例單獨處理
接下來的情況就是,正常的隨機分錢,為了盡量讓每個人分錢的概率差不多,用了下邊的方法
a.先將錢按當前分錢的人數計算平均值
b.隨機的錢數的取值范圍是(1,平均值)
c.可以分配的總錢數減去生成的隨機錢數,得到下一次分配時的可分配總錢數
d.重復a~c步驟,最終完成隨機分配
按照以上的方法隨機分完之后,消耗的總金額是一定小於等於輸入的總金額的,那么,在處理完隨機分配之后,還要對剩余的金額處理
這里實現的,就是將剩余的金額,再用平均分配的方式,分散到每一個人的手里
以上就是實現發紅吧的大致思路,下邊代碼,就是根據這個思路整理而成
一、rand_money方法,完成一次隨機分配
1 /* 2 * 隨機分錢 3 * 參數:$money,參與分錢的金額 4 * $num,參與分錢的人數 5 */ 6 function rand_money($money, $num) 7 { 8 $arr = [];//結果數組 9 $money = $money * 100;// 將元轉成分(小數計算有誤差,隨機數也都是整數) 10 $rest_money = $money;// 初始化,剩余錢的變量 11 $average = $rest_money / $num;// 求出均分情況下,每人的紅包值 12 13 if ($average < 1) {// 錢不夠所有人分 14 return false; 15 }elseif($average == 1){// 所有人平均分這筆錢(錢數只夠這么分的) 16 for ($i=0; $i<$num; $i++) { 17 $arr[] = $average; 18 } 19 }else{// 每個人隨機分配 20 for ($i=0; $i<$num; $i++) { 21 $range_money = round(($rest_money / ($num - $i))); 22 $rand_money = mt_rand(1, $range_money); 23 $arr[] = $rand_money; 24 $rest_money = $rest_money - $rand_money;// 獲取剩下的錢 25 } 26 } 27 return $arr; 28 }
二、average_money方法,既可以自己完成平均分配,又可以協助隨機分配,完成剩余金額的分配
1 function average_money($money, $num, $arr=[], $conversion_val=1) 2 { 3 $money = $money * 100; 4 $arr_sum = 0;//保存數組和 5 if (count($arr) > 0) {// 隨機分配,會調用此方法將剩余的錢分掉,此數組為隨機分配后的結果 6 foreach ($arr as $k=>$v) { 7 $arr[$k] = $v * 100 / $conversion_val;// 如果單位有變化這調整一下,一直以分為單位處理數據 8 } 9 $arr_sum = array_sum($arr);// 統計隨機分配已經分配了總錢數 10 } else { 11 for ($i=0; $i<$num; $i++) { 12 $arr[] = 0;// 初始化每個人的數組,兼容下邊循環處理部分 13 } 14 } 15 $add_money = $money - $arr_sum; 16 // 如果總錢數和之前隨機分配的數組的總和差值為0,就說明隨機分配已經將錢全部分出去了,就不需要再平均分配處理了 17 if ($add_money == 0) { 18 return $arr; 19 } 20 // 先把剩余的能均分的部分均分一下,然后若再有剩余,則從前到后,注意分配 21 for ($i = 0; $i < $num; $i++) { 22 $arr[$i] = $arr[$i] + floor($add_money / $num);// 如果之前有隨機分配,則是將剩余的錢平均追加入隨機分配的值里 23 } 24 $arr_sum = array_sum($arr);// 分配后,求和,用於修正最后剩余的零錢 25 // 如果還有剩余,這部分說明每人一分都不夠,就從頭開始沒人一分的分下去,直到分完為止 26 $odd_money = bcsub($money, $arr_sum, 0);// 針對錢的計算,建議使用bc函數,普通的計算方法有誤差 27 $i = 0; 28 while ($odd_money >= 1) { 29 $arr[$i] = $arr[$i] + 1;// 每人加1分錢 30 $odd_money = $odd_money - 1;// 剩余的金額,每分掉一個人,就減1分錢 31 $i++; 32 } 33 return $arr; 34 }
三、紅包調用方法,根據不同類型,返回不同紅包的結果
1 /* 2 * 紅包方法 3 * 參數:$money,參與分錢的金額 4 * $num,參與分錢的人數 5 * $type,紅包類型,0平均分配,1隨機分配 6 */ 7 function get_red ($money, $num, $type=0) { 8 if ($type) { // 非0,隨機紅包 9 $arr_rand = rand_money($money, $num);// 先隨機分配 10 $arr = average_money($money, $num, $arr_rand, 100);// 再平均分配 11 } else { // 平均分配紅包 12 $arr = average_money($money, $num); 13 } 14 return $arr; 15 }
四、實例代碼測試
1 $a = get_red(66.61, 11, 0); 2 //將最終結果,轉換成元為單位 3 foreach ($a as $k=>$val) { 4 $a[$k] = $val / 100; 5 } 6 print_r($a); 7 echo '<br />'.array_sum($a); 8 9 $r = get_red(66.61, 11, 1); 10 //將最終結果,轉換成元為單位 11 foreach ($r as $k=>$val) { 12 $r[$k] = $val / 100; 13 } 14 echo '<br />'; 15 print_r($r); 16 echo '<br />'.array_sum($r);
以上的代碼,就完成了紅包的操作。
這代碼只是,簡單的實現。這其中還有特殊情況,比如,每次隨機的數都是最小的數,雖然概率很低。
那么這種情況,只做一次隨機分配,貌似效果並不好。畢竟后邊就是平均分配了,這樣每一個人的終值非常接近平均值。
所以,可以考慮,在一次隨機分配之后,計算已分配總錢數,根據該總錢數判斷是否需要再次進行隨機分配,然后將兩次或者多次隨機分配的值同key合並。
最后再把剩余的金額“平均分配”后,同key加到一起。這樣的結果效果更好。
注意:
1、日常人們習慣金錢的單位都是“元”,但這里,盡量轉成“分”;小數計算誤差大,隨機數生成也都是整數
2、如果可以,金錢在計算時,盡量使用bc高精度函數。如:bcadd(加),bcsub(減),bcmul(乘),bcdiv(除)等