js数组实现权重概率分配


Posted in Javascript onSeptember 12, 2017

今天写了一个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版本如果只是使用整数数字比模式,完全不用考虑数字的放大与求最小公倍数的算法,只需要做简单的累加即可,可以大大缩短执行时间。

Javascript 相关文章推荐
JavaScript 捕获窗口关闭事件
Jul 26 Javascript
在JQuery dialog里的服务器控件 事件失效问题
Dec 08 Javascript
jQuery学习总结之元素的相对定位和选择器(持续更新)
Apr 26 Javascript
jquery如何把数组变为字符串传到服务端并处理
Apr 30 Javascript
jQuery根据ID获取input、checkbox、radio、select的示例
Aug 11 Javascript
整理Javascript基础语法学习笔记
Nov 29 Javascript
JavaScript的Vue.js库入门学习教程
May 23 Javascript
深入浅析Node.js单线程模型
Jul 10 Javascript
webpack热模块替换(HMR)/热更新的方法
Apr 05 Javascript
详解vue-cli中模拟数据的两种方法
Jul 03 Javascript
基于js Canvas实现二次贝塞尔曲线
Dec 25 Javascript
仿iPhone通讯录制作小程序自定义选择组件的实现
May 23 Javascript
js 概率计算(简单版)
Sep 12 #Javascript
JavaScript面向对象精要(下部)
Sep 12 #Javascript
在Vue.js中使用Mixins的方法
Sep 12 #Javascript
JavaScript面向对象精要(上部)
Sep 12 #Javascript
JS库之ParticlesJS使用简介
Sep 12 #Javascript
关于预加载InstantClick的问题解决方法
Sep 12 #Javascript
提升页面加载速度的插件InstantClick
Sep 12 #Javascript
You might like
php 数组动态添加实现代码(最土团购系统的价格排序)
2011/12/30 PHP
PHP排序算法的复习和总结
2012/02/15 PHP
PHP sprintf() 函数的应用(定义和用法)
2012/06/29 PHP
PHP的5个安全措施小结
2012/07/17 PHP
PHP将XML转数组过程详解
2013/11/13 PHP
编写PHP脚本使WordPress的主题支持Widget侧边栏
2015/12/14 PHP
JavaScript 大数据相加的问题
2011/08/03 Javascript
jquery 关于event.target使用的几点说明介绍
2013/04/26 Javascript
解决jquery操作checkbox火狐下第二次无法勾选问题
2014/02/10 Javascript
javascript trim函数在IE下不能用的解决方法
2014/09/12 Javascript
JavaScript的jQuery库插件的简要开发指南
2015/08/12 Javascript
jquery实现简洁文件上传表单样式
2015/11/02 Javascript
String字符串截取的四种方式总结
2016/11/28 Javascript
js中setTimeout的妙用--防止循环超时
2017/03/06 Javascript
JS中的多态实例详解
2017/10/15 Javascript
ES6 javascript中class静态方法、属性与实例属性用法示例
2017/10/30 Javascript
详解webpack 入门与解析
2018/04/09 Javascript
layui实现table加载的示例代码
2018/08/14 Javascript
微信小程序实现顶部下拉菜单栏
2018/11/04 Javascript
js 根据对象数组中的属性进行排序实现代码
2019/09/12 Javascript
vue如何实现动态加载脚本
2020/02/05 Javascript
微信小程序实现拼图小游戏
2020/10/22 Javascript
python教程之用py2exe将PY文件转成EXE文件
2014/06/12 Python
python PyTorch参数初始化和Finetune
2018/02/11 Python
python 实现绘制整齐的表格
2019/11/18 Python
Python 支持向量机分类器的实现
2020/01/15 Python
windeln官方海外旗舰店:德淘超人气母婴超市
2017/12/15 全球购物
新秀丽官方旗舰店:Samsonite拉杆箱、双肩包、皮具
2018/03/05 全球购物
日本著名的服饰鞋帽综合类购物网站:MAGASEEK
2019/01/09 全球购物
Brother加拿大官网:打印机、贴标机、缝纫机
2019/10/09 全球购物
实习教师自我鉴定
2013/12/09 职场文书
编辑求职信样本
2013/12/16 职场文书
2014两会学习心得:时代的发展
2014/03/17 职场文书
2014年文学毕业生自我鉴定
2014/04/23 职场文书
阿里云Nginx配置https实现域名访问项目(图文教程)
2021/03/31 Servers
Python Pandas常用函数方法总结
2021/06/15 Python