c++随机打乱数组_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); 

结果如下:

f4e9d103cb466c449be93bd700fa43ec.png

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

输出结果如下:

9ed7a437ab1b8efc13f0790edd61cd99.png

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

结果如下:

9c5de9275f4102c735f9c9b1515e60b7.png

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

d7454988cce5b9208ee3d1aaa2254213.png

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

f74bb8e81a01d628607efec14b303a1a.png

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

f8ffb208114589952816d5bfb80584e6.png

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

c32827dcd387709a5f346f8e8af89d8d.png

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

64820a295da628121b5d997f4f8ec9d3.png

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

4113ee8bfb7c0ec4bba05b1149c5e6b2.png

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

运行结果如下:

ab0caa81f91f8d84eb96534071cdc207.png

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

结果如下:

434cfa0baeb594cda4ba64515e098368.png

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

4 机选彩票

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

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

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

dee6d8a20575b63d2d5f93d061ae46bd.gif
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值