①二分法
将最大值二分(除以二)以后,就能保证数组一直运转到没法分割为止。二分法是永远分不完的。只要把概率一直分下去,最后余下的概率全加到最后一个键就ok。
然后就有了以下代码:
//Func:二分法生成指定长度指定总和的数组。
//@length:数组长度。
//@sum:整个数组所有键值的总和。
function midRandom(length, sum) {
var mid = sum / 2;
let arr = [];
for (let i = 0; i < length - 1; i++) {
arr[i] = Math.round(Math.random() * mid);
var remain = mid - arr[i];
var mid = (remain + mid) / 2;
}
var arrsum = arr.reduce(function(prev, cur, index, array) {
return prev + cur
})
arr[length - 1] = sum - arrsum;
return arr;
}
有一说一问题不大,但你会发现生成只是伪·随机。它的随机是没法达到接近最大值的(要保证后面生成的数还能继续生成,前面的数就永远不能取最大值,只能取接近最大值)。所以就有了以下思路
②扣去几率法
//Func:最大近似值取指定长度指定总和的数组
//@length:数组长度
//@sum:数组键值总和
function nearMaxRandom(length, sum) {
var safe = sum - length;
let arr = [];
for (let i = 0; i < length - 1; i++) {
arr[i] = Math.round(Math.random() * safe);
var remain = safe - arr[i];
var safe = remain + 1;
}
var arrsum = arr.reduce(function(prev, cur, index, array) {
return prev + cur
})
arr[length - 1] = sum - arrsum;
return arr;
}
这个确实是达到了每次生成的数值都能顶着前一个数字生成后扣去几率后的近似最大值。它的思路是设置一个可生成的最大值的安全近似范围,每次生成数值后为后面每个数值保留1的几率以应对最坏情况。
但你会发现随着要求的数组长度越来越长,这玩意生成的数组,后面几乎是一排0了。
于是我就在想,能不能生成一个将总和平均分成数组长度份数的,只在规定范围内波动的相对“公平”的数组。
于是又有了以下代码
③公平范围内随机法
//Func:生成键值在一定范围内波动的,指定长度,指定总和的数组
//@length: 数组长度
//@sum: 数组键值总和
//@float: 允许键值波动范围,浮点数,0-1之间,数值越大波动越不稳定
function fairRandom(length, sum, float) {
var aCopie = sum / length;
let arr = [];
var addon = 0;
for (let i = 0; i < length - 1; i++) {
var exc = Math.round(Math.random() * float * (aCopie - addon));
Math.random() >= 0.5 ? addon = 0 - exc : addon = exc;
arr[i] = Math.round(aCopie + addon);
}
var arrsum = arr.reduce(function (prev, cur, index, array) {
return prev + cur
})
arr[length - 1] = sum - arrsum;
return arr;
}
出人意料的是,这次相当丝滑,我用fairRandom(10,100,0.1)
生成了十次,键值们都有很乖巧的在小范围内随机浮动,看起来整整齐齐,没有0和特别大的数存在,相当赏心悦目。
这段代码的思路则是首先将键值每份是多少计算出来,然后生成浮动范围,且浮动范围和上次生成有关。这就直接造成了后面生成的数据只会反复横跳小范围浮动,做到了相对公平随机的地步。