当前位置: 首页 > news >正文

JavaScript4种数组随机选取实战源码

在我们的实际开发工作中,经常都会使用到数组随机选取的功能需求,比如彩票随机一注,随机五注,机动车号牌随机选一个等等。

接下来的内容,来给大家分享一下,我们在开发过程中,常用到的4种数组随机选取实现方案,这些实现方案都是在实战中应用的,非常值得大家学习和收藏,我们一起来看看都有哪些方法吧!

1 使用Math.random()

这种方式是使用Array.sort()和Math.random()结合的方法,Math.random()返回的是一个0-1之间(不包括1)的伪随机数,注意这不是真正的随机数。

var letter = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'];
function shuffle1(arr) {
    return arr.sort(() => 0.5 - Math.random())
}
console.time("shuffle1");
letter = shuffle1(letter);
console.timeEnd("shuffle1");
console.log(letter);

这种方式并不是真正的随机,来看下面的例子。对这个10个字母数组排序1000次,假设这个排序是随机的话,字母a在排序后的数组中每个位置出现的位置应该是1000/10=100,或者说接近100次。看下面的测试代码:

let n = 1000;
let count = (new Array(10)).fill(0);
for (let i = 0; i < n; i++) {
    let letter = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'];
    letter.sort(() => Math.random() - 0.5);
    count[letter.indexOf('a')]++
}
console.log(count); 

结果如下:

JavaScript4种数组随机选取实战源码「干货」

可以看出元素a的位置在0到9出现的次数并不是接近100的。

原因有两点:

  1. Math.random()方法产生的伪随机数并不是在0到1之间均匀分布,不能提供像密码一样安全的随机数字。
  2. Array.prototype.sort(compareFunction)方法中的compareFunction(a, b)回调必须总是对相同的输入返回相同的比较结果,否则排序的结果将是不确定的。

这里sort(() => 0.5 - Math.random())没有输入,跟谈不上返回相同的结果,所以这个方法返回的结果不是真正的数组中的随机元素。

2 随机值排序

既然(a, b) => Math.random() - 0.5 的问题是不能保证针对同一组 a、b 每次返回的值相同,那么我们不妨将数组元素改造一下,比如将元素'a'改造为{ value: 'a', range: Math.random() },数组变成[{ value: 'a', range: 0.10497314648454847 }, { value: 'b', range: 0.6497386423992171 }, ...],比较的时候用这个range值进行比较,这样就满足了Array.sort()的比较条件。

代码如下:

let letter = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'];
function shuffle2(arr) {
    let new_arr = arr.map(i => ({value: i, range: Math.random()}));
    new_arr.sort((a, b) => a.r - b.r);
    arr.splice(0, arr.length, ...new_arr.map(i => i.value));
}
console.time("shuffle2");
letter = shuffle2(letter);
console.timeEnd("shuffle2");
console.log(shuffle2); 

输出结果如下:

JavaScript4种数组随机选取实战源码「干货」

我们再使用上面的方式测试一下,看看元素a元素是不是随机分布的。

let n = 1000, count = (new Array(10)).fill(0);
for (let i = 0; i < n; i++) {
    let letter = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'];
    letter = shuffle2(letter)
    count[letter.indexOf('a')]++
}
console.log(count); 

结果如下:

JavaScript4种数组随机选取实战源码「干货」

从这里可以看出,元素a在位置0到9出现的次数是接近100的,也就是说元素a是随机分布的,其他的元素也是,这时再从这个新数组中截取前几个元素就是想要的数组了。

3 洗牌算法

上面的sort算法,虽然满足了随机性的需求,但是性能上并不是很好,很明显为了达到随机目的把简单数组变成了对象数组,最后又从排序后的数组中获取这个随机数组,明显走了一些弯路。

洗牌算法可以解决随机性问题,洗牌算法的步骤如下:

  1. 数组arr,有n个元素,存放从1到n的数值;
  2. 生成一个从0到n-1的随机数x;
  3. 输出arr下标为x的元素,即第一个随机数;
  4. 将arr的尾元素和下标为x的元素值互换;
  5. 同步骤2,生成一个从0到n-2的随机数x;
  6. 输出arr下标为x的数组,第二个随机数;
  7. 将arr倒数第二个元素和下标为x的元素互换;
  8. 重复执行直至输出m个数为止;

洗牌算法是真的随机的吗,换言之洗牌算法真的可以随机得到n个元素中m个吗?下面拿一个只有5个元素的数组来说明。

数组有5个元素,如下图。

JavaScript4种数组随机选取实战源码「干货」

从5个元素随机抽出一个元素和最后一个换位,假设抽到3,概率是1/5,如下图。注意其他任意4个元素未被抽到的概率是4/5。最终3出现在最后一位的概率是1/5。

JavaScript4种数组随机选取实战源码「干货」

将抽到的3和最后一位的5互换位置,最后一位3就确定了,如下图:

JavaScript4种数组随机选取实战源码「干货」

再从前面不确定的4个元素随机抽一个,这里注意要先考虑这4个元素在第一次未被抽到的概率是4/5,再考虑本次抽到的概率是1/4,然后乘一下得到1/5。注意其他任意3个未被抽到的概率是3/4。5出现在倒数第二位的概率是4/5*1/4=1/5如下图:

JavaScript4种数组随机选取实战源码「干货」

现在最后2个元素确定了,从剩下的3个元素中任意抽取一个,概率是1/3,身下任意2个未被抽到的概率是2/3,但是要考虑上一次未被抽到的概率是3/4,以及上上一次未被抽到的概率是4/5,于是最终1出现在倒数第三位的概率是1/3*3/4*4/5=1/5。

JavaScript4种数组随机选取实战源码「干货」

现在倒数3个元素已经确定,剩下的2个元素中任意取一个,概率是1/2,但是要考虑上一次未被抽到的概率是2/3,上上一次未被抽到的概率是3/4,上上上一次未被抽到的概率是4/5,最终4出现在倒数第4位的概率是1/2*2/3*3/4*4/5=1/5。

JavaScript4种数组随机选取实战源码「干货」

最后还剩下一个2,它出现在倒数第5位的概率肯定也是1/5。不嫌啰嗦的话可以继续看下去。

现在倒数4个元素已经确定,剩下1个元素中任意取一个,概率是1,但要考虑上一次未被抽中的概率是1/2,上上一次未被抽中的概率是2/3,上上上一次未被抽中的概率是3/4,上上上上一次未被抽中的概率是4/5,于是2出现在倒数5位置的概率是1*1/2*2/3*3/4*4/5=1/5。

有了算法,下面给出洗牌算法的代码:

let letter = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'];
function shuffle3(arr) {
    let i = arr.length, t, j;
    while (i) {
        j = Math.floor(Math.random() * (i--)); //
        t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }
}
console.time("shuffle3");
shuffle3(letter);
console.timeEnd("shuffle3");
console.log(letter) 

运行结果如下:

JavaScript4种数组随机选取实战源码「干货」

还有最后一个问题,我们来验证一下,还是和上面的方法一样,随机排序1000次,看看字母a出现在0-9个位置的概率是多少,理论上应该是1000/10=100。

来看下面的代码:

let n = 1000;
let count = (new Array(10)).fill(0);
function shuffle3(arr) {
    let i = arr.length, t, j;
    while (i) {
        j = Math.floor(Math.random() * (i--)); //
        t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }
}
for (let i = 0; i < n; i++) {
    let letter = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'];
    shuffle3(letter);
    count[letter.indexOf('a')]++
}
console.log(count); 

结果如下:

JavaScript4种数组随机选取实战源码「干货」

可以看到基本上都是接近100的,可以说明洗牌算法是随机的。

4 机选彩票

最近开发做了一个模拟彩票游戏的功能,彩票有机选,其实就是随机选取,下面以双色球为例来看看实现的效果是什么样的。

双色球前区从1到33个小球,后区从1到16个小球,一注彩票中前区至少选6个,后区至少选1个。

这里使用洗牌算法实现,如下图:

JavaScript4种数组随机选取实战源码「干货」

相关文章:

  • Python 教程之 Numpy(1)—— 什么是 Numpy?
  • $refs 、$nextTic、动态组件、name的使用
  • C语言实现简单通讯录,malloc,calloc,realloc,free动态内存分配的学习。
  • 【新人报到】【学习笔记】Python编程——入门
  • 【金融】中国vix、skew指数的Python实现
  • 外汇天眼:英镑暴跌英国国债暴跌 英国新政府宣布激进的减税促经济计划
  • 基于React的富文本编辑器——Braft Editor使用
  • vue项目docker打包通过k8s
  • 论文教程之阅读科学论文11步法,详细介绍了每个部分的关注点
  • [云原生] [kubernetes] 基于K8S安装kubesphere
  • docker部署服务初体验
  • 每日学一个设计模式23——解释器模式
  • C. Minimum Notation #823 div2
  • Python 教程之 Numpy(2)—— 数组
  • 【踩坑】在linux服务器上配置mysql并开放3306端口
  • 【跃迁之路】【519天】程序员高效学习方法论探索系列(实验阶段276-2018.07.09)...
  • 〔开发系列〕一次关于小程序开发的深度总结
  • DOM的那些事
  • dva中组件的懒加载
  • input的行数自动增减
  • java正则表式的使用
  • Redux 中间件分析
  • 番外篇1:在Windows环境下安装JDK
  • 力扣(LeetCode)21
  • 原生JS动态加载JS、CSS文件及代码脚本
  • #图像处理
  • (教学思路 C#之类三)方法参数类型(ref、out、parmas)
  • (论文阅读11/100)Fast R-CNN
  • (排序详解之 堆排序)
  • (一)Thymeleaf用法——Thymeleaf简介
  • (转)iOS字体
  • (转)Spring4.2.5+Hibernate4.3.11+Struts1.3.8集成方案一
  • (转)德国人的记事本
  • ****Linux下Mysql的安装和配置
  • *p=a是把a的值赋给p,p=a是把a的地址赋给p。
  • .NET 4 并行(多核)“.NET研究”编程系列之二 从Task开始
  • .net 4.0发布后不能正常显示图片问题
  • .NET/C# 使用反射调用含 ref 或 out 参数的方法
  • .pyc文件还原.py文件_Python什么情况下会生成pyc文件?
  • @ConfigurationProperties注解对数据的自动封装
  • [android] 切换界面的通用处理
  • [C++] Boost智能指针——boost::scoped_ptr(使用及原理分析)
  • [CentOs7]iptables防火墙安装与设置
  • [CVPR 2023:3D Gaussian Splatting:实时的神经场渲染]
  • [Docker]四.Docker部署nodejs项目,部署Mysql,部署Redis,部署Mongodb
  • [EFI]MSI GF63 Thin 9SCXR电脑 Hackintosh 黑苹果efi引导文件
  • [GN] Vue3快速上手1
  • [Hadoop in China 2011] 蒋建平:探秘基于Hadoop的华为共有云
  • [hdu 3652] B-number
  • [Head First设计模式]策略模式
  • [iOS]Win8下iTunes无法连接iPhone版本的解决方法
  • [LeetCode]-使用特殊算法的题目-2
  • [PHP]pearProject协作系统 v2.8.14 前后端
  • [PHP]禅道项目管理软件ZenTaoPMS源码包 v16.4
  • [SPOJ]COT2