百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分类 > 正文

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

ztj100 2024-11-25 00:38 13 浏览 0 评论

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

接下来的内容,来给大家分享一下,我们在开发过程中,常用到的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); 

结果如下:

可以看出元素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); 

输出结果如下:

我们再使用上面的方式测试一下,看看元素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); 

结果如下:

从这里可以看出,元素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个元素,如下图。

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

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

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

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

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

最后还剩下一个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) 

运行结果如下:

还有最后一个问题,我们来验证一下,还是和上面的方法一样,随机排序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); 

结果如下:

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

4 机选彩票

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

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

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

相关推荐

Vue 技术栈(全家桶)(vue technology)

Vue技术栈(全家桶)尚硅谷前端研究院第1章:Vue核心Vue简介官网英文官网:https://vuejs.org/中文官网:https://cn.vuejs.org/...

vue 基础- nextTick 的使用场景(vue的nexttick这个方法有什么用)

前言《vue基础》系列是再次回炉vue记的笔记,除了官网那部分知识点外,还会加入自己的一些理解。(里面会有部分和官网相同的文案,有经验的同学择感兴趣的阅读)在开发时,是不是遇到过这样的场景,响应...

vue3 组件初始化流程(vue组件初始化顺序)

学习完成响应式系统后,咋们来看看vue3组件的初始化流程既然是看vue组件的初始化流程,咋们先来创建基本的代码,跑跑流程(在app.vue中写入以下内容,来跑流程)...

vue3优雅的设置element-plus的table自动滚动到底部

场景我是需要在table最后添加一行数据,然后把滚动条滚动到最后。查网上的解决方案都是读取html结构,暴力的去获取,虽能解决问题,但是不喜欢这种打补丁的解决方案,我想着官方应该有相关的定义,于是就去...

Vue3为什么推荐使用ref而不是reactive

为什么推荐使用ref而不是reactivereactive本身具有很大局限性导致使用过程需要额外注意,如果忽视这些问题将对开发造成不小的麻烦;ref更像是vue2时代optionapi的data的替...

9、echarts 在 vue 中怎么引用?(必会)

首先我们初始化一个vue项目,执行vueinitwebpackechart,接着我们进入初始化的项目下。安装echarts,npminstallecharts-S//或...

无所不能,将 Vue 渲染到嵌入式液晶屏

该文章转载自公众号@前端时刻,https://mp.weixin.qq.com/s/WDHW36zhfNFVFVv4jO2vrA前言...

vue-element-admin 增删改查(五)(vue-element-admin怎么用)

此篇幅比较长,涉及到的小知识点也比较多,一定要耐心看完,记住学东西没有耐心可不行!!!一、添加和修改注:添加和编辑用到了同一个组件,也就是此篇文章你能学会如何封装组件及引用组件;第二能学会async和...

最全的 Vue 面试题+详解答案(vue面试题知识点大全)

前言本文整理了...

基于 vue3.0 桌面端朋友圈/登录验证+60s倒计时

今天给大家分享的是Vue3聊天实例中的朋友圈的实现及登录验证和倒计时操作。先上效果图这个是最新开发的vue3.x网页端聊天项目中的朋友圈模块。用到了ElementPlus...

不来看看这些 VUE 的生命周期钩子函数?| 原力计划

作者|huangfuyk责编|王晓曼出品|CSDN博客VUE的生命周期钩子函数:就是指在一个组件从创建到销毁的过程自动执行的函数,包含组件的变化。可以分为:创建、挂载、更新、销毁四个模块...

Vue3.5正式上线,父传子props用法更丝滑简洁

前言Vue3.5在2024-09-03正式上线,目前在Vue官网显最新版本已经是Vue3.5,其中主要包含了几个小改动,我留意到日常最常用的改动就是props了,肯定是用Vue3的人必用的,所以针对性...

Vue 3 生命周期完整指南(vue生命周期及使用)

Vue2和Vue3中的生命周期钩子的工作方式非常相似,我们仍然可以访问相同的钩子,也希望将它们能用于相同的场景。...

救命!这 10 个 Vue3 技巧藏太深了!性能翻倍 + 摸鱼神器全揭秘

前端打工人集合!是不是经常遇到这些崩溃瞬间:Vue3项目越写越卡,组件通信像走迷宫,复杂逻辑写得脑壳疼?别慌!作为在一线摸爬滚打多年的老前端,今天直接甩出10个超实用的Vue3实战技巧,手把...

怎么在 vue 中使用 form 清除校验状态?

在Vue中使用表单验证时,经常需要清除表单的校验状态。下面我将介绍一些方法来清除表单的校验状态。1.使用this.$refs...

取消回复欢迎 发表评论: