JavaScript에서 배열의 요소 중 일부를 중복 없이 무작위로 추출해야 하는 경우가 자주 있습니다. 예를 들어, 다음과 같이 중복 요소가 없는 배열 리터럴이 있다고 가정해 보겠습니다.
const arr = [2, 5, 4, 45, 32, 46, 78, 87, 98, 56, 23, 12];
문제 정의
우리가 작성해야 할 함수는 두 가지 인수를 받습니다.
- 고유한(unique) 값들로만 이루어진 배열
- 추출할 요소의 개수 n
함수는 입력 배열에서 무작위로 선택한 n개의 요소로 구성된 새로운 배열을 반환해야 하며, 어떤 요소도 결과 배열에 두 번 이상 등장해서는 안 됩니다.
구현 방법
핵심 아이디어는 간단합니다. 원하는 개수만큼 반복하면서 매번 Math.random()과 Math.floor()를 사용해 배열 내 임의의 인덱스를 생성하고, 이미 선택된 요소인지 검사한 후 중복되지 않은 경우에만 결과 배열에 추가합니다.
const arr = [2, 5, 4, 45, 32, 46, 78, 87, 98, 56, 23, 12];
const chooseRandom = (arr, num = 1) => {
const res = [];
for(let i = 0; i < num; ){
const random = Math.floor(Math.random() * arr.length);
if(res.indexOf(arr[random]) !== -1){
continue;
};
res.push(arr[random]);
i++;
};
return res;
};
console.log(chooseRandom(arr, 4));
코드 설명
- 기본값 처리: 두 번째 인수
num이 전달되지 않으면 기본값 1이 사용되므로, 개수를 지정하지 않아도 요소 하나를 안전하게 추출할 수 있습니다. - 무작위 인덱스 생성:
Math.floor(Math.random() * arr.length)는 0부터 배열 길이 - 1 사이의 정수 인덱스를 만듭니다. - 중복 검사:
res.indexOf(arr[random]) !== -1조건으로 이미 선택된 요소인지 확인하고, 중복이라면continue로 건너뛰어 카운터를 증가시키지 않습니다.
실행 결과
콘솔에서 위 코드를 실행하면 다음과 같은 형태의 출력을 얻을 수 있습니다. 실행할 때마다 선택되는 요소와 순서는 달라집니다.
[ 5, 2, 4, 78 ]
참고 사항
이 방식은 직관적이지만, 요청한 개수(n)가 배열 길이보다 크면 무한 루프에 빠질 수 있다는 점에 유의해야 합니다. 실무에서는 Fisher-Yates 셔플 알고리즘으로 배열을 섞은 뒤 앞에서 n개를 잘라내는 방식이 더 효율적이고 안전합니다.