1、面试题javascript
问:有一个长度为 100 的数组,如何从中随机挑选 50 个元素,组成一个新的数组?html
答:这个...那个...emmmmmm前端
问:那先不挑 50 个,就挑一个数,知道怎么作吗?java
答:这个我知道!随机生成一个 0 ~ 99 的数,而后去原数组取对应位置的元素就能够了~面试
let randomIndex = arr[Math.floor(Math.random() * arr.length)];
问:好,回到最初的问题,怎么挑选 50 个元素?算法
答:我知道了,在 0 ~ 99 的范围内,随机生成 50 个不重复的数字!segmentfault
问:是这个思路,具体的实现呢?记得保证效率哦。数组
答:(吧啦吧啦吧啦)dom
问:如今假设数组的元素都是 String 类型,若是要把这个数组元素的顺序打乱,有什么办法么?ide
答:数组的 sort() 方法能够传入一个函数做为参数,这个函数的返回值能够决定排列顺序。在这个函数中写一个随机数,而后就能乱序了。
问:这是一个思路,但这只是伪随机。
答:啊咧?
问:据说过“洗牌算法”吗?
2、随机取数
按照上面随机挑选一个数的思路,从原数组中随机抽取一个数,而后使用 splice 删掉该元素
function getRandomArrElement(arr, count) { let res = [] while (res.length < count) { // 生成随机 index
let randomIdx = (Math.random() * arr.length) >> 0; // splice 返回的是一个数组
res.push(arr.splice(randomIdx, 1)[0]); } return res }
上面生成随机 index 用到了按位右移操做符 >>
当后面的操做数是 0 的时候,该语句的结果就和 Math.floor() 同样,是向下取整
但位操做符是在数值表示的最底层执行操做,所以速度更快
// 按位右移
(Math.random() * 100) >> 0
// Math.floor
Math.floor(Math.random() * 100) /* 这两种写法的结果是同样的,但位操做的效率更高 */
3、经过 sort 乱序
首先认识一下 Array.prototype.sort()
这个方法能够传入一个参数 compareFunction,这个参数必须是函数
同时 sort() 会暴露出 Array 中的两个元素 (a, b) 做为参数传给 compareFunction
sort() 会根据 compareFunction(a, b) 的返回值,来决定 a 和 b 的相对位置:
根据以上规则,能够在 compareFunction 中生成一个随机数,而后根据随机数作运算,返回一个正负未知的 Number,从而实现乱序
function randomSort(a,b) { return .5 - Math.random(); } let arr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; arr.sort(randomSort);
但这并非真正的乱序,计算机的 random 函数由于循环周期的存在,没法生成真正的随机数
4、Fisher–Yates shuffle 洗牌算法
洗牌算法的思路是:
先从数组末尾开始,选取最后一个元素,与数组中随机一个位置的元素交换位置
而后在已经排好的最后一个元素之外的位置中,随机产生一个位置,让该位置元素与倒数第二个元素进行交换
以此类推,打乱整个数组的顺序
function shuffle(arr) { let len = arr.length; while (len) { let i = (Math.random() * len--) >> 0;
// 交换位置 let temp = arr[len]; arr[len] = arr[i]; arr[i] = temp; } return arr; }
再结合 ES6 的解构赋值,使用洗牌算法就更方便了:
Array.prototype.shuffle = function() { let m = this.length, i; while (m) { i = (Math.random() * m--) >>> 0; [this[m], this[i]] = [this[i], this[m]] } return this; }
5、用洗牌算法随机取数
再回到从长度为 100 的数组中取 50 个数的问题
以前用的是 splice 修改原数组,若是结合洗牌算法,又会有别的思路
最好是本身先思考一下,而后再展开代码进行比较
function getRandomArrElement(arr, count) { let shuffled = arr.slice(0), i = arr.length, min = i - count, temp, index; while (i > min) { index = Math.floor((i--) * Math.random()); temp = shuffled[index]; shuffled[index] = shuffled[i]; shuffled[i] = temp; } return shuffled.slice(min); }
最后放个彩蛋,关于两种随机取数的性能孰优孰劣
我用 Array.form 生成了一个长度为一百万的数组,而后从中随机取十万个数
首先是使用 splice 的方案:
而后是洗牌算法:
喵喵喵?!!
附录:
补充一个在范围内生成随机数的方法:
setRangeRandom(min: number, max: number) { //在范围内生成随机数 let n = max - min; if (n == 0) { return max } else if (n < 0) { [max, min] = [min, max]; n = Math.abs(n); } return ((Math.random() * ++n) >> 0) + min; }
参考资料: