原理:主要是将数组里的索引值随机打乱,然后将当前的索引值与随机变化之后的索引值互换。
1.首先遍历的开始是从最大的索引开始,然后逐次递减;
2.然后选取一个随机值randomIndex,这个随机值的产生是在0-len(即数组的长度)之间产生,由于这个值不能为len(因为数组的索引是从0开始的),只能为len-1,故只能向下取整Math.floor;
3.取到随机值之后,将这个随机值对应的数组值即arr[randomIndex]赋值给当前遍历的i对应的数组值即arr[i];
提示:这里要注意第三步,有的同学看到第三步之后,会这样写:arr[i] = arr[randomIndex]。这样写会导致一个什么样的错误呢?这样写会导致输出的数组中部分元素的值相同,如下图所示:
这是因为每一次遍历的时候,randomIndex是随机产生的,但是并不妨碍它们在这几次之中有个别几次是取到相同的值的,就比如上图中,randomIndex取值为了1的时候出现了三次,因为原数组中arr[1]=1,变化之后的数组中有三个1,所以randomIndex是取了三次1并分别赋值给了arr[i],其中i=5,i=3,i=2。
因此,要先定义个变量itemAtIndex来先保存arr[randomIndex]的值,然后将当前遍历的i对应的值赋值给索引值randomIndex对应的数组值,即arr[randomIndex] = arr[i],最后将之前保存arr[randomIndex]的值的itemAtIndex赋值给arr[i]
最终代码如下:
function getArrRandomly(arr) {
var len = arr.length;
//首先从最大的数开始遍历,之后递减
for(var i=len-1;i>=0;i--){
//随机索引值randomIndex是从0-arr.length中随机抽取的
var randomIndex = Math.floor(Math.random() * (i+1));
//下面三句相当于把从数组中随机抽取到的值与当前遍历的值互换位置
var itemIndex = arr[randomIndex];
arr[randomIndex] = arr[i];
arr[i] = itemIndex;
}
//每一次的遍历都相当于把从数组中随机抽取(不重复)的一个元素放到数组的最后面(索引顺序为:len-1,len-2,len-3......0)
return arr;
},
最后,爱思考的同学可能就会问一个问题了。为什么遍历的时候要从i的取值最大时开始呢?可不可以就从i=0开始呢?
答案只能说勉强可以,但是不推荐。思路是差不多的,这里就不再赘述,实现如下代码:
function getArrRandomly(arr) {
var len = arr.length;
//首先从最小的数开始遍历,之后递增
for (var i = 0; i < len; i++) {
var randomIndex = Math.floor(Math.random()*(len-i));//这里一定要注意,后面不管是(i+1)还是(len-i),它们是时变的。
var itemAtIndex = arr[randomIndex];
arr[randomIndex] = arr[i];
arr[i] = itemAtIndex;
}
//每一次遍历,都相当于把从数组中随机抽取(不重复,因为)一个元素放到数组的最前面(索引顺序为0,1,2...len-1)
return arr;
}
//下面是调用这个函数
var arr = [0,1,2,3,4,5,6,7,8];
var newArray = getArrRandomly(arr);
console.log(newArray)
为什么不推荐用遍历递增的形式呢?
因为遍历递减的话,从最大的索引值开始,比如该问题中从i=8开始,不管随机值取什么(0-8之间),这个随机索引对应的数组值是被放入到arr[8]的位置上的;然后在第二次遍历的时候,i=7,这个时候,这个随机值只能取0-7之间的值,因为i是递减的,大家可以从var randomIndex = Math.floor(Math.random() * (i+1))可以看出,所以随机值是取不到8的,故arr[8]这个值就实锤了,也就是说了第二次遍历完后,arr[7]的位置也会被实锤,直到arr[0]实锤完毕。
而遍历递减就不同了,它无法实现arr[i]某个索引值实锤,就比如上面的代码,var randomIndex = Math.floor(Math.random() * (len-i)),这里randomIndex是连续九次都可以取到0这个值,无法一次性实锤。虽然这个方法最后也会把数组打乱,但是细心的同学会发现,不管运行几次程序,打乱后的数组的第一个元素,不是8就是0。所以这个方法并不推荐。