JavaScript数组随机取一部分不重复元素

从一个 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); //[ 18, 16, 12, 17, 14 ]

解决思路就是从第二次随机抽取的元素开始,将抽取的元素与已抽取元素相比较,如果相同,则重新抽取,并再次执行比较的操作。
但是这种写法存在循环语句和条件语句多层嵌套,复杂度较,执行效率很。随着元素的抽取越多,要比较的次数越来越多,“失败的抽取”概率越来越大。

我们可以优化一下比较的逻辑,比如以下代码:

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); //[ 16, 17, 13, 11, 18 ]

和第一种方法相比,节省了第一种方法中依次比较的步骤,但依旧存在“失败抽取”的现象,而且失败抽取的概率没有发生任何变化。

是否可以把抽取到的元素从数组中删除,从而避免重复抽取呢?可以利用 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;
}

完整代码