? ? ? ?今天中午在群里看到一個有意思的題目搓译,寫個簡單的函數(shù):把整數(shù)100隨機(jī)分成10份锋喜。有點(diǎn)像微信紅包,10塊的紅包隨機(jī)分給5個人轴总。
? ? ? ?乍一看這個題目感覺很簡單博个,無非是用mt_rand函數(shù)生成隨機(jī)數(shù)⊥ぃ可是真的動手運(yùn)行起來發(fā)現(xiàn)還真是不那么簡單共耍,第一個問題是不能有負(fù)數(shù)或0產(chǎn)生(不做限制很容易出現(xiàn)負(fù)數(shù)或0),第二個問題是每個隨機(jī)數(shù)必須是整數(shù)(與微信紅包就不一樣了)穆咐,第三個問題是保證每份的生成都是隨機(jī)且隨機(jī)范圍是一樣的(這才是最大的難點(diǎn))。如果你感覺能輕松越過雷區(qū)对湃,那么不妨先看一下這幾中錯誤思路。
方法一:
$number=100;
$num=array();
$total=0;
for($i=0;$i<10;$i++){
$num[$i]=mt_rand(1,$number);
}
print_r($num);
非常明顯的錯誤心傀,因?yàn)槊總€生成數(shù)都沒有限制拆讯,所以10個數(shù)的總和肯定大于100。
方法二:
$number=100;
$num=array();
$total=0;
for($i=0;$i<10;$i++){
$num[$i]=mt_rand(1,$number-$total);
$total=$num[$i]+$total;
}
print_r($num);
同方法一相比雖然做了限制宰翅,但是還是不夠全面爽室,結(jié)果會出現(xiàn)多個1且總和仍會超過100。
吸取了這兩個錯誤掉缺,再進(jìn)行一步規(guī)范就可以得到正確方法三:
$number=100;
$num=array();
$total=0;
for($i=0;$i<10;$i++){
$num[$i]=mt_rand(1,$number-$total-10+$i+1);
$total=$num[$i]+$total;
}
print_r($num);
這個方法已經(jīng)做了很明確的限制保證每個隨機(jī)數(shù)至少不是0或是負(fù)數(shù)且總和等于100戈擒。同樣還有方法四:
$number=100;
$num=array();
$total=0;
for($i=0;$i<10;$i++){
If($i<5){
$num[$i]=mt_rand(1,19);
}else{
$num[$i]=mt_rand(1,$number-$total-10+$i+1);
}
$total=$num[$i]+$total;
}
print_r($num);
這個方法保證了前5個生成數(shù)的隨機(jī)范圍一樣且10個數(shù)總和為100∷汛眩可是這兩個方法的缺點(diǎn)都是10個生成數(shù)隨機(jī)范圍不一樣柑土,所以方法三運(yùn)行結(jié)果越是前面數(shù)字越大越往后越小。方法四前5個數(shù)字都是小于19扮宠,而后5個數(shù)字也會出現(xiàn)越往后越小的現(xiàn)象狐榔。
? ? ? ?綜合前面幾種例子,我們需要一種同時生成10個隨機(jī)數(shù)的方法收捣。當(dāng)然庵楷,這種方法還不存在楣颠。但是我們可以生成10個隨機(jī)范圍一樣的數(shù)字咐蚯,然后根據(jù)這個10個隨機(jī)數(shù)占總份額的比例來均分整數(shù)100.
方法五:
$number=100;
$num=array();
$num_tmp=array();
$total=0;
$total_tmp=0;
for($i=0;$i<10;$i++){
$num_tmp[$i]=mt_rand(1,10);
$total_tmp=$num_tmp[$i]+$total_tmp;
}
for($j=0;$j<10;$j++){
$num[$j]=$number*$num_tmp[$j]/$total_tmp;
}
print_r($num);
運(yùn)用這個方法可以實(shí)現(xiàn)每個隨機(jī)數(shù)的取值范圍相等且總和為100;但是問題又來了,因?yàn)榘幢壤≈禃霈F(xiàn)小數(shù)所以生成的隨機(jī)數(shù)違背了整數(shù)原則睁冬。如果我們強(qiáng)制用ceil,floor,round,無論是小數(shù)進(jìn)一位看疙,舍去或是四舍五入都不能保證總數(shù)和為100直奋。所以退而求次只好小數(shù)舍一位然后最后一個數(shù)取余數(shù)。
方法六:
$number=100;
$num=array();
$num_tmp=array();
$total=0;
$total_tmp=0;
for($i=0;$i<10;$i++){
$num_tmp[$i]=mt_rand(1,10);
$total_tmp=$num_tmp[$i]+$total_tmp;
}
for($j=0;$j<9;$j++){
$num[$j]=floor($number*$num_tmp[$j]/$total_tmp);
$total=$num[$j]+$total;
}
$num[9]=$number-$total;
print_r($num);
? ? ? ?除了這個方法搁胆,我們再換個思路邮绿,把整數(shù)100劃分100份,讓每一份隨機(jī)分給10個組顾腊,最終每個組的總數(shù)就是10個生成的隨機(jī)數(shù)挖胃。
方法七:
$number=100;
$num=array();
for($i=0;$i<100;$i++){
$tmp=mt_rand(1,10);
$num[$tmp]++;
}
print_r($num);
這個方法看起來寫得最簡單,而且優(yōu)點(diǎn)明顯吗垮,總數(shù)和為100凹髓,每個數(shù)為整數(shù),隨機(jī)范圍一樣蔚舀,可是它的致命缺點(diǎn)就是有可能生成隨機(jī)數(shù)0(雖然概率特別特別谢雀摇)。還有一個缺點(diǎn)是運(yùn)行速度會慢一點(diǎn)寿谴,因?yàn)橐h(huán)100次。不過一般小項(xiàng)目可以忽略咏瑟。接下來針對可能生成0的致命缺點(diǎn),需要想辦法來完善兄旬。
方法八:$number=100;
$num=array();
for($i=0;$i<$number-10;$i++){
$tmp=mt_rand(0,9);
$num[$tmp]++;
}
for($j=0;$j<10;$j++){
$num[$j]++;
}
print_r($num);
方法八的改善就是預(yù)先給每個生成數(shù)預(yù)留值1余寥,等隨機(jī)分配完之后再加上這個值⌒髂欤可以避免出現(xiàn)生成0的隨機(jī)數(shù)祝蝠。根據(jù)預(yù)留值的思路,我又想到另外一種方法可以提升運(yùn)行速度绎狭,
方法九:$number=100;
$num=array();
for($i=0;$i<9;$i++){
$tmp[$i]=mt_rand(0,$number-10);
}
sort($tmp);
$num[0]=$tmp[0]+1;
for($j=1;$j<9;$j++){
$num[$j]=$tmp[$j]-$tmp[$j-1]+1;
}
$num[9]=90-$tmp[8]+1;
print_r($num);
方法十用的類似一種“卡位”,想像有100張撲克牌疊在一起喇聊。然后用9張書簽隨機(jī)卡位社付,分成10份唱蒸。每份的張數(shù)就是隨機(jī)生成數(shù)。由于卡位可能兩張書簽卡在一處啼辣,出現(xiàn)‘0’的隨機(jī)生成數(shù),所以仍然需要預(yù)留10張撲克牌党远,用90張牌卡位富弦,最后把預(yù)留的10張牌均分給每份。就形成了上面的方法腕柜,一共只用了17次循環(huán)。
綜上所述砰蠢,用最后兩種方法就能達(dá)到隨機(jī)分配的效果。歡迎提出意見或有更好的方法分享律杠。