JavaScript4种数组随机选取实战源码
Posted JackieDYH
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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);
结果如下:
可以看出元素a的位置在0到9出现的次数并不是接近100的。
原因有两点:
- Math.random()方法产生的伪随机数并不是在0到1之间均匀分布,不能提供像密码一样安全的随机数字。
- 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算法,虽然满足了随机性的需求,但是性能上并不是很好,很明显为了达到随机目的把简单数组变成了对象数组,最后又从排序后的数组中获取这个随机数组,明显走了一些弯路。
洗牌算法可以解决随机性问题,洗牌算法的步骤如下:
- 数组arr,有n个元素,存放从1到n的数值;
- 生成一个从0到n-1的随机数x;
- 输出arr下标为x的元素,即第一个随机数;
- 将arr的尾元素和下标为x的元素值互换;
- 同步骤2,生成一个从0到n-2的随机数x;
- 输出arr下标为x的数组,第二个随机数;
- 将arr倒数第二个元素和下标为x的元素互换;
- 重复执行直至输出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个。
这里使用洗牌算法实现,如下图:
以上是关于JavaScript4种数组随机选取实战源码的主要内容,如果未能解决你的问题,请参考以下文章
js实现随机选取[10,100)中的10个整数,存入一个数组,并排序。 另考虑(10,100]和[10,100]两种情况。