js高效生成一组随机数(不重复)

        昨天做了这么一个功能:在3*3的表格里面生成9个1-9的随机数,并且9个数字不重复,由于时间紧张,只简单的实现了功能,部分代码如下:

//Java script语言

var nums=[];
function getNum(){
    var s;
    do{
        s=Math.ceil(Math.random()*1000000000%9);
        console.log(s);
    }while(nums.includes(s))
    nums[nums.length]=s;
    return s;

        很显然,这样的效率是非常低的。因为到了最后一个数的时候,会有8/9的几率如生成已经生成的数,do whiel 循环会继续进行下去,直到生成了符合条件的数。而且随着生成的数的数量越多,对性能的耗费就越大。需要生成100000个这样的数的话,那么,程序的性能就立马能体现出来。

      于是,改进了一下代码(部分):

      var nums=[];
for(j=0;j<9;j++)
{
    nums[j]=j+1;
}

function getNum(){
    s=Math.floor(Math.random()*nums.length);
    m=nums[s];
    nums.splice(s,1);
    return m;
}

        改进后的代码再使用while 循环来生成符合条件的数字,但是,又有新的问题出现了:代码中倒数第三行,每调用一次该函数,都会将数组中的元素删除一个,要知道,删除数组的元素也是非常消耗性能的。因为删除数组元素的时候需要将数组进行遍历,重新排列索引。

        看来代码还需要重新改进一下下,就差那么一丁点儿了。经过一番简单的思考,个人认为,js中的对象可以再利用一下,于是便有了如下代码:

var nums=new Object();
for(j=0;j<9;j++)
{
    nums[j]=j+1;
}

function getNum(){
    s=Math.floor(Math.random()*Object.keys(nums).length);
    
    m=nums[s];
    console.log(m)
    delete nums.s;
    return m;
}

        代码看似没有什么问题了,但是,经过测试发现代码的性能并没有多大的提高,原因可能是在获取对象长度的时候,因为需要遍历整个对象才能获得。也想到过用链表(Linked list)但是,链表对于数据查找的性能很低,而数组对于数据随机删除和添加的效率很低,对于这个程序来说,两者并没有多大的区别(关于链表(Linked list)和数组Array List会在以后的文章中发表)。

       就在思路陷入绝境的时候,突然灵光一闪,为什么非得要删除数组中的元素呢?每次使用该方法的时候,将用过的元素放于数组的最后,不再取用不就完了。对,很不错的一个解决方法:

var nums=[];
var len=9;
var n=1;                        //函数调用次数,没调用一次加1,用来限制索引最大值。
for(j=0;j<len;j++)
{
    nums[j]=j+1;
}

function getNum(){
    ind=len-n;
    s=Math.floor(Math.random()*(ind));
    swap(s,ind);                //将随机取到的元素放到未取用部分的最后
    n++;                           //下次就不会取用到交换后的元素
    return nums[ind];        //直接将交换后的元素返回
}

function swap(start,target){
    t=nums[start];
    nums[start]=nums[target];
    nums[target]=t;
}

        因为每次获取数组长度都是一致的,干脆直接定义变量,直接使用,节省性能。具体请看注释。

        经过测试,程序完整的执行时间由原来的8~15毫秒缩短到3~8毫秒,终于成功了!

---------------------------------6.28更新------------------

       想要提高程序的性能,其实特别简单,尽可能的减少循环,遍历等,程序的性能就能显著提高。

到此为止,虽然需求和性能都实现了,但是代码的复用性并不强,而且容易出现异常。当数组中的元素被取用完之后,这时候便再也没有元素可取,整个程序就会崩溃。如果要将它应用到别的项目上,可能还需要修改很多参数。

        于是便有了如下封装:

/**
 * an Object to create a lists of number    which continuity within a certain range
 * @param int min the min number
 * @param int mmax the max number
 * @return this.
 */
function serialRand(min,max)
{
    var nums=[];
    var n=1;                  //the next num to invoke getNum function
    var len=max-min+1;        //the length of the array

    /**
     * init array
     */
    for (i=0;i<(len) ;i++ )
    {
        nums[i]=min++;
    }

    /**
     * make the  function private prevent Exceptioni(Example IndexOutOfBoundsException Exception)
     * @return int.
     */
    var getNum=function()
    {
        ind=len-n;
        s=getRand(0,ind);
        n++;
        swap(ind,s);
        console.log(nums[ind])
        return nums[ind];
    }
    /**
     * this method returns a rand num between m and a .
     * @param int m the min value of the num that u want.
     * @param int a the max value of the num that u want.
     * @return int a random number.
     * */
    var getRand=function(m,a)
    {
        return Math.floor(Math.random()*(a-m)+m)
    }
    /**
     * change a and b in array nums
     * @param int a the index of element
     * @param int b the index of another element
     * @return void
     */
    var swap=function(a,b)
    {
        t=nums[a];
        nums[a]=nums[b];
        nums[b]=t;
    }

    this.hasNext=function()
    {
        if(n>len)
        {
            return false;
        }
        else
        {
            return true;
        }
    }

    this.next=function()
    {
        if(n>len)
        {
            throw ("index out of Bournd");
            return;
        }
        else
        {
            return getNum();
        }
    }
    return this;
}

        封装后的代码看似非常多,其实就加了两个方法,然后将所有方法私有化,只开放hasNext方法用于循环和next方法用于取值。然后其余的就是注释了。

        有人说,代码里边的注释没有什么用处,其实代码里边的注释还是特别有用的,因为有了注释,读者才能对代码的功能参数等一目了然,而不用去直接阅读代码。而且方便更新维护。个人在思路明确的情况下一般先写方法名,再写注释,所有的方法名和注释写完之后再去实现方法的功能。这样一来,思路就不容易被打断。

        好了,言归正传。虽然代码封装完了,看似也没有什么bug,但是必要的测试还是需要的。

        经过一系列测试,有些不敢相信自己的眼睛,封装后的性能比以前低了不少,甚至比第一次写出来的代码还要慢!

        这样的结果让我有点儿懵,刚开始是用方法去构造对象,再后来就是这么低的性能,js面向对象让我有点儿怀疑世界,简直不敢恭维。通过观察发现,其实就是一个方法,里边放了很多的方法和局部变量而已。那么。this关键字是面向对象的东西,但是为什么在方法里边也可以使用,这就不得而知了,但是我试了一下,将new 关键字去掉,再去剩下的代码也能继续执行。至于私有化,那纯属是局部变量只能在方法内部访问罢了。

        嗯,人生一次失败的封装。突然感觉被js坑了,拿个方法过来就能new真的有种随心所欲的感觉(可能是对js的理解不够吧)。

        通过多番查找资料了解到,javascript虽然是面向对象的语言,但是JavaScript不使用类,它不会像php和java那样,通过类来创建对象,而是基于prototype来创建对象。也就是说,在js中,一个方法(函数)就是一个对象。

        js中的面向对象比其他语言的面向对象更为复杂,在使用new 关键字创建对象的时候,js会自动创建一个对象,并且会调用该方法作为构造方法。使用var 关键字部分,只是属于函数,并不会包含在对象中。每当调用next或hasNext方法的时候,相当于间接调用serialRand方法中的函数。也就是说,封装的类中只有两个方法,然而真正有用的部分在构造函数里边,构造函数里边的其它内容是属于函数本身而不是对象的。

        看来等有空的时候该系统的学习一下js的面向对象了。

猜你喜欢

转载自blog.csdn.net/huang_cheng_zhi/article/details/93903098