从一个 JavaScript 数组当中,随机抽取部分元素,构成新数组,要求这些元素不能重复,即随机获取不重复的数组元素。
这个问题很简单,相信很多人都会在几分钟内给出以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function randomMembers1(arr, limit) { const result = [];
for (let i = 0; i < limit; i++) { result[i] = arr[Math.floor(Math.random() * arr.length)]; for (let j = 0; j < i; j++) { if (result[j] === result[i]) { i--; break; } } } return result; }
randomMembers1([11, 12, 13, 14, 15, 16, 17, 18], 5);
|
解决思路就是从第二次随机抽取的元素开始,将抽取的元素与已抽取元素相比较,如果相同,则重新抽取,并再次执行比较的操作。
但是这种写法存在循环语句和条件语句多层嵌套,复杂度较高,执行效率很低。随着元素的抽取越多,要比较的次数越来越多,“失败的抽取”概率越来越大。
我们可以优化一下比较的逻辑,比如以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function randomMembers2(arr, limit) { const hash = {}; const result = [];
while (limit > 0) { const index = Math.floor(Math.random() * arr.length); if (!hash[index]) { hash[index] = true; result.push(arr[index]); limit--; } }
return result; }
randomMembers2([11, 12, 13, 14, 15, 16, 17, 18], 5);
|
和第一种方法相比,节省了第一种方法中依次比较的步骤,但依旧存在“失败抽取”的现象,而且失败抽取的概率没有发生任何变化。
是否可以把抽取到的元素从数组中删除,从而避免重复抽取呢?可以利用 splice 方法,将抽取到的元素从数组当中删除掉,并把返回值存储(push)到结果数组当中。
1 2 3 4 5 6 7 8 9 10 11 12 13
| function randomMembers3(arr, limit) { const result = []; let num = arr.length > limit ? limit : arr.length;
while (num > 0) { const index = Math.floor(Math.random() * arr.length); result.push(arr.splice(index, 1)[0]);
num--; }
return result; }
|
问题就是这种方法会修改源数组,产生副作用,抽取的元素会从数组中删除。我们可以新建一个数组保存原来数组的下标,从下标数组中进行抽取。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function randomMembers4(arr, limit) { const result = []; const keyList = [...arr.keys()]; let num = arr.length > limit ? limit : arr.length;
while (num > 0) { const index = Math.floor(Math.random() * keyList.length); const key = keyList.splice(index, 1)[0];
result.push(arr[key]);
num--; }
return result; }
|
完整代码