JavaScript洗牌算法之重复抽奖

十年踪迹的最新博客:5分钟现场撸代码——谈总结会抽奖程序,实际上就是一个洗牌算法,描述为从一个数组中随机抽取几个数出来,我之前写过的一个文章:js随机从数组中取出几个元素 正好符合上面的要求,看了他的代码,延伸出来的另外一种就是可以重复抽奖。学习一下,顺便复习一下以前写过的东西。

我原来简单的代码是这样子:

function getRandomArrayElements(arr, count) {
    var shuffled = arr.slice(0), i = arr.length, min = i - count, temp, index;
    while (i-- > min) {
        index = Math.floor((i + 1) * Math.random());
        temp = shuffled[index];
        shuffled[index] = shuffled[i];
        shuffled[i] = temp;
    }
    return shuffled.slice(min);
}


var items = ['1','2','4','5','6','7','8','9','10'];
console.log( getRandomArrayElements(items, 4) );

逻辑:

1.首先从整个数组中抽取一个随机值,然后把这个值和最后面一个没有查过的项交换位置

2.循环遍历的结果就是所有随机值都放到最后面,循环次数为要取的随机数组长度。

3.最后把最后面的数组取出来就得到我们的随机数组了。

十年踪迹的代码使用到了ES6

代码一:

const cards = Array(62).fill().map((_,i)=>i+1); //初始化一个 1~62 的数组

function draw(n = 1){ // 一次抽取 n 个,默认一次 1 个
    var ret = [];
    for(var i = 0; i < n; i++){
        let idx = Math.floor(cards.length * Math.random());
        ret.push(...cards.splice(idx, 1));
    }
    return ret;
}
console.log(draw(10)); //抽取一次,10个中奖者

1.其中ret.push(…cards.splice(idx,1));使用到了上一节讲到的ES6扩展符,这个会组合两个数组的值,

2.cards.splice(idx,1)返回的是删除的元素的数组,使用数组splice()方法,原数组cards也会发生变化,即原数组已经去掉了idx这个项。

3.接着循环的时候,cards.length缩小,idx,也会从剩下的项中取。

代码二:

代码二和代码三跟我最上面的做法差不多,通过洗牌算法

function draw(amount, n = 1){
    const cards = Array(amount).fill().map((_,i)=>i+1); 

    for(let i = amount - 1, stop = amount - n - 1; i > stop; i--){
        let rand = Math.floor((i + 1) * Math.random());
        [cards[rand], cards[i]] =  [cards[i], cards[rand]];
    }
    return cards.slice(-n);
}
console.log(draw(62, 10));

其中使用到了ES6的解构,解构提供了一个方便地从对象或数组中提取数据的方法。

let [x, y] = [1, 2]; // x = 1, y = 2
 
// ES5 equivalent:
var arr = [1, 2];
var x = arr[0];
var y = arr[1];

使用这个语法,可以一次性给多个变量赋值。一个很好的附加用处是可以很简单地交换变量值:

let x = 1,
   y = 2;

[x, y] = [y, x]; // x = 2, y = 1

代码四:

需求是可以分开多次来取值,则需要用到了构造函数来初始化,通过方法来获取值。

function Box(amount){
    this.cards = Array(amount).fill().map((_,i)=>i+1); 
}
Box.prototype.draw = function(n = 1){
    let amount = this.cards.length, cards = this.cards;

    for(let i = amount - 1, stop = amount - n - 1; i > stop; i--){
        let rand = Math.floor((i + 1) * Math.random());
        [cards[rand], cards[i]] =  [cards[i], cards[rand]];
    }

    let ret = cards.slice(-n);    
    cards.length = amount - n;

    return ret;
}

var box = new Box(62);
console.log(box.draw(5), box.draw(5)); //一次取 5 个,取 2 次

这个没什么好说的,下面我附上一个我写的代码:

function RandomArr(amount) {
    this.arr = Array(amount).fill().map((_, i) = >i + 1);
}
RandomArr.prototype.getCount = function(count) {
    var arr = this.arr,
    len = arr.length,
    min = len - count,
    index;
    while (len-->min) {
        index = Math.floor((len + 1) * Math.random()); 
        [arr[index], arr[len]] = [arr[len], arr[index]];
    }
    var ret = arr.slice(min);
    arr.length = min;
    return ret
}

var rand = new RandomArr(62);
console.log(rand.getCount(4), rand.getCount(6));

有一些东西,叫我十分钟现场写出来,可能写不出来吧。看来还是要多掌握基础的东西。比如这个洗牌算法

function shuffle(array) {
    var currentIndex = array.length
      , temporaryValue
      , randomIndex
      ;

    // While there remain elements to shuffle...
    while (0 !== currentIndex) {

      // Pick a remaining element...
      randomIndex = Math.floor(Math.random() * currentIndex);
      currentIndex -= 1;

      // And swap it with the current element.
      temporaryValue = array[currentIndex];
      array[currentIndex] = array[randomIndex];
      array[randomIndex] = temporaryValue;
    }

    return array;
  }

本文纯粹水文一篇,欢迎到十年踪迹的原文看他的思路:https://www.h5jun.com/post/luckey-draw-in-5-minutes.html


关注我

我的微信公众号:前端开发博客,在后台回复以下关键字可以获取资源。

  • 回复「小抄」,领取Vue、JavaScript 和 WebComponent 小抄 PDF
  • 回复「Vue脑图」获取 Vue 相关脑图
  • 回复「思维图」获取 JavaScript 相关思维图
  • 回复「简历」获取简历制作建议
  • 回复「简历模板」获取精选的简历模板
  • 回复「加群」进入500人前端精英群
  • 回复「电子书」下载我整理的大量前端资源,含面试、Vue实战项目、CSS和JavaScript电子书等。
  • 回复「知识点」下载高清JavaScript知识点图谱

每日分享有用的前端开发知识,加我微信:caibaojian89 交流