js数组实现权重概率分配

今天写了一个js控制页面轮播的功能,如果仅仅使用队列很简单,但是考虑到为每一个页面分配权重的是否变的异常复杂,使用switch和if else也无法解决,于是想到使用js数组实现,思路是将各个轮播的页面抽象成一个对象,各个对象需要手动指定权重值,然后组成一个数组,使用下面封装的函数,将会根据各个对象相应的权重概率返回一个对象,代码如下:

/**

* js数组实现权重概率分配

* @param Array arr js数组,参数类型[Object,Object,Object……]

* @return Array 返回一个随机元素,概率为其percent/所有percent之和,参数类型Object

* @author shuiguang

*/

function weight_rand(arr){

//参数arr元素必须含有percent属性,参考如下所示

/*

var arr = [{

name : '1',

percent : 1

}, {

name : '2',

percent : 2

}, {

name : '3',

percent : 1

}, {

name : '4',

percent : 2

}

];

*/

var total = 0;

var i, j, percent;

//下标标记数组,按照上面的例子,单倍情况下其组成为[1,2,2,3,4,4]

var index = new Array();

for (i = 0; i < arr.length; i++) {

//判断元素的权重,为了实现小数权重,先将所有的值放大100倍

percent = 'undefined' != typeof(arr[i].percent) ? parseInt(arr[i].percent*100) : 0;

for (j = 0; j < percent; j++) {

index.push(i);

}

total += percent;

}

//随机数值,其值介于0-5的整数

var rand = Math.floor(Math.random() * total);

return arr[index[rand]];

}

上面的方法虽然可行,可是遇到这样一个问题:对于一般复杂的分配情况如1:1:1分配(相对值)可以满足,如果遇到15%,25%,35%剩余等精确权重分配(绝对值)无法满足。因为去计算15%:25%:35%:剩余的比例很是麻烦,于是我将上面的函数继续修改,添加了百分比模式,比如上面的例子,分配了上面明确的百分数之后,剩余的百分比将给最后一个元素,而不用计算最后一个元素占的百分数,也不用计算各个元素的比例。代码如下:

/**

* js数组实现权重概率分配,支持数字比模式(支持2位小数)和百分比模式(不支持小数,最后一个元素多退少补)

* @param Array arr js数组,参数类型[Object,Object,Object……]

* @return Array 返回一个随机元素,概率为其weight/所有weight之和,参数类型Object

* @author shuiguang

*/

function weight_rand(arr){

//参数arr元素必须含有weight属性,参考如下所示

//var arr=[{name:'1',weight:1.5},{name:'2',weight:2.5},{name:'3',weight:3.5}];

//var arr=[{name:'1',weight:'15%'},{name:'2',weight:'25%'},{name:'3',weight:'35%'}];

//求出最大公约数以计算缩小倍数,perMode为百分比模式

var per;

var maxNum = 0;

var perMode = false;

//自定义Math求最小公约数方法

Math.gcd = function(a,b){

var min = Math.min(a,b);

var max = Math.max(a,b);

var result = 1;

if(a === 0 || b===0){

return max;

}

for(var i=min; i>=1; i--){

if(min % i === 0 && max % i === 0){

result = i;

break;

}

}

return result;

};

//使用clone元素对象拷贝仍然会造成浪费,但是使用权重数组对应关系更省内存

var weight_arr = new Array();

for (i = 0; i < arr.length; i++) {

if('undefined' != typeof(arr[i].weight))

{

if(arr[i].weight.toString().indexOf('%') !== -1) {

per = Math.floor(arr[i].weight.toString().replace('%',''));

perMode = true;

}else{

per = Math.floor(arr[i].weight*100);

}

}else{

per = 0;

}

weight_arr[i] = per;

maxNum = Math.gcd(maxNum, per);

}

//数字比模式,3:5:7,其组成[0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2]

//百分比模式,元素所占百分比为15%,25%,35%

var index = new Array();

var total = 0;

var len = 0;

if(perMode){

for (i = 0; i < arr.length; i++) {

//len表示存储arr下标的数据块长度,已优化至最小整数形式减小索引数组的长度

len = weight_arr[i];

for (j = 0; j < len; j++){

//超过100%跳出,后面的舍弃

if(total >= 100){

break;

}

index.push(i);

total++;

}

}

//使用最后一个元素补齐100%

while(total < 100){

index.push(arr.length-1);

total++;

}

}else{

for (i = 0; i < arr.length; i++) {

//len表示存储arr下标的数据块长度,已优化至最小整数形式减小索引数组的长度

len = weight_arr[i]/maxNum;

for (j = 0; j < len; j++){

index.push(i);

}

total += len;

}

}

//随机数值,其值为0-11的整数,数据块根据权重分块

var rand = Math.floor(Math.random()*total);

//console.log(index);

return arr[index[rand]];

}

var arr=[{name:'1',weight:1.5},{name:'2',weight:2.5},{name:'3',weight:3.5}];

console.log(weight_rand(arr));

var arr=[{name:'1',weight:'15%'},{name:'2',weight:'25%'},{name:'3',weight:'35%'}];

console.log(weight_rand(arr));

var prize_arr = [

{'id':1, 'prize':'平板电脑', 'weight':1},

{'id':2, 'prize':'数码相机', 'weight':2},

{'id':3, 'prize':'音箱设备', 'weight':10},

{'id':4, 'prize':'4G优盘', 'weight':12},

{'id':5, 'prize':'10Q币', 'weight':22},

{'id':6, 'prize':'下次没准就能中哦', 'weight':50}

];

var times = 100000;

var prize;

var pingban = 0;

var shuma = 0;

var yinxiang = 0;

var youpan = 0;

var qb = 0;

var xc = 0;

var start = new Date().getTime();

for($i=0; $i<times; $i++){

prize = weight_rand(prize_arr);

if(prize.prize == '平板电脑')

{

pingban++;

}else if(prize.prize == '数码相机'){

shuma++;

}else if(prize.prize == '音箱设备'){

yinxiang++;

}else if(prize.prize == '4G优盘'){

youpan++;

}else if(prize.prize == '10Q币'){

qb++;

}else if(prize.prize == '下次没准就能中哦'){

xc++;

}

}

var stop = new Date().getTime();

console.log('平板电脑:'+pingban/times+', 数码相机:'+shuma/times+', 音箱设备:'+yinxiang/times+', 4G优盘:'+youpan/times+', 10Q币:'+qb/times+', 下次没准就能中哦:'+xc/times);

console.log('耗费时间:'+(stop-start)/1000+'秒');

该代码已经通过最大公约数对下标数组进行优化,使用数字比模式已经优化到最小数值比例,百分比模式考虑性能消耗暂不支持2位小数。

写完js版,于是很轻松改为php版本,经过10万次循环测试,发现for循环比foreach省时间,而非网上传的foreach比for更快。但是总体来说,js的执行速度是php的20倍左右,php的执行时间约6秒,js的执行时间约为0.346秒。

/**

* php数组实现权重概率分配,支持数字比模式(支持2位小数)和百分比模式(不支持小数,最后一个元素多退少补)

* @param array $arr php数组,参数类型array(array(),array(),array()……)

* @return array 返回一个随机元素,概率为其percent/所有percent之和,参数类型array()

* @author shuiguang

*/

function weight_rand($arr)

{

//参数arr元素必须含有percent属性,参考如下所示

//$arr=array(array('name'=>'1','weight'=>1.5),array('name'=>'2','weight'=>1.5),array('name'=>'3','weight'=>1.5));

//$arr=array(array('name'=>'1','weight'=>'15%'),array('name'=>'2','weight'=>'25%'),array('name'=>'3','weight'=>'35%'));

//求出最大公约数以计算缩小倍数,perMode为百分比模式

$perMode = false;

$maxNum = 0;

//自定义求最小公约数方法

$gcd = function($a, $b)

{

$min = min($a, $b);

$max = max($a, $b);

$result = 1;

if($a === 0 || $b === 0)

{

return $max;

}

for($i=$min; $i>=1; $i--)

{

if($min % $i === 0 && $max % $i === 0)

{

$result = $i;

break;

}

}

return $result;

};

//使用传地址可能会影响后面的结果,但是使用权重数组对应关系更省内存

$weight_arr = array();

$arr_len = count($arr);

for($i=0; $i<$arr_len; $i++)

{

if(isset($arr[$i]['weight']))

{

if(strpos($arr[$i]['weight'], '%') !== false)

{

$per = floor(str_replace('%', '', $arr[$i]['weight']));

$perMode = true;

}else{

$per = floor($arr[$i]['weight']*100);

}

}else{

$per = 0;

}

$weight_arr[$i] = $per;

$maxNum = call_user_func($gcd, $maxNum, $per);

}

//数字比模式,3:5:7,其组成[0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2]

//百分比模式,元素所占百分比为15%,25%,35%

$index = array();

$total = 0;

if($perMode)

{

for($i=0; $i<$arr_len; $i++)

{

//$len表示存储$arr下标的数据块长度,已优化至最小整数形式减小索引数组的长度

$len = $weight_arr[$i];

for ($j = 0; $j < $len; $j++)

{

//超过100%跳出,后面的舍弃

if($total >= 100)

{

break;

}

$index[] = $i;

$total++;

}

}

//使用最后一个元素补齐100%

while($total < 100)

{

$index[] = $arr_len-1;

$total++;

}

}else{

for($i=0; $i<$arr_len; $i++)

{

//len表示存储arr下标的数据块长度,已优化至最小整数形式减小索引数组的长度

$len = $weight_arr[$i]/$maxNum;

for ($j = 0; $j < $len; $j++)

{

$index[] = $i;

}

$total += $len;

}

}

//随机数值,其值为0-11的整数,数据块根据权重分块

$rand = floor(mt_rand(0, $total));

//修复php随机函数可以取临界值造成的bug

$rand = $rand == $total ? $total-1 : $rand;

return $arr[$index[$rand]];

}

$arr=array(array('name'=>'1','weight'=>1.5),array('name'=>'2','weight'=>1.5),array('name'=>'3','weight'=>1.5));

p(weight_rand($arr));

$arr=array(array('name'=>'1','weight'=>'15%'),array('name'=>'2','weight'=>'25%'),array('name'=>'3','weight'=>'35%'));

p(weight_rand($arr));

$prize_arr = array(

'0' => array('id'=>1, 'prize'=>'平板电脑', 'weight'=>1),

'1' => array('id'=>2, 'prize'=>'数码相机', 'weight'=>5),

'2' => array('id'=>3, 'prize'=>'音箱设备', 'weight'=>10),

'3' => array('id'=>4, 'prize'=>'4G优盘', 'weight'=>12),

'4' => array('id'=>5, 'prize'=>'10Q币', 'weight'=>22),

'5' => array('id'=>6, 'prize'=>'下次没准就能中哦', 'weight'=>50),

);

$start = time();

$result = array();

$times = 100000;

for($i=0; $i<$times; $i++)

{

$row = weight_rand($prize_arr);

if(array_key_exists($row['prize'], $result))

{

$result[$row['prize']] ++;

}else{

$result[$row['prize']] = 1;

}

}

$cost = time() - $start;

p($result);

p('耗费时间:'.$cost.'秒');

function p($var)

{

echo "<pre>";

if($var === false)

{

echo 'false';

}else if($var === ''){

print_r("''");

}else{

print_r($var);

}

echo "</pre>";

}

php版本如果只是使用整数数字比模式,完全不用考虑数字的放大与求最小公倍数的算法,只需要做简单的累加即可,可以大大缩短执行时间。

以上是 js数组实现权重概率分配 的全部内容, 来源链接: utcz.com/z/336940.html

回到顶部