이 글에서는 리터럴 값으로 이루어진 배열을 받아, 그 요소들의 순서를 제자리(in-place)에서 무작위로 섞어주는 JavaScript 함수를 작성해 보겠습니다.
여기서 '제자리 섞기'란 새로운 배열을 생성하지 않고 원본 배열 자체의 순서를 직접 변경한다는 의미입니다.
구현 예제
해당 기능을 구현한 코드는 다음과 같습니다.
const letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
const unorderArray = arr => {
let i, pos, temp;
for (i = 0; i < 100; i++) {
pos = Math.random() * arr.length | 0;
temp = arr[pos];
arr.splice(pos, 1);
arr.push(temp);
};
}
unorderArray(letters);
console.log(letters);
코드 동작 원리
Math.random() * arr.length | 0: 0 이상 배열 길이 미만 범위의 임의의 정수 인덱스를 생성합니다.splice(pos, 1): 해당 인덱스에 있는 요소를 배열에서 제거합니다.push(temp): 제거했던 요소를 배열의 맨 뒤에 다시 추가합니다.- 이 과정을 총 100회 반복하면서 배열의 순서가 점차 무작위로 뒤섞이게 됩니다.
실행 결과
콘솔에 출력되는 결과는 다음과 같습니다.
[
'b', 'e', 'c',
'a', 'g', 'f',
'd'
]
위 출력은 가능한 여러 결과 중 하나일 뿐이라는 점에 유의하세요. 프로그램을 실행할 때마다 배열이 섞이는 순서는 매번 달라집니다.
참고: 피셔-예이츠(Fisher–Yates) 셔플
위 방식은 구현이 간단하지만, 반복 횟수와 추출 방식의 특성상 결과에 약간의 편향이 생길 수 있습니다. 모든 순열이 수학적으로 동일한 확률로 나오도록 보장하려면 피셔-예이츠 알고리즘을 사용하는 것이 좋습니다.
const fisherYatesShuffle = arr => {
for (let i = arr.length - 1; i > 0; i--) {
const j = Math.random() * (i + 1) | 0;
[arr[i], arr[j]] = [arr[j], arr[i]];
}
};
fisherYatesShuffle(letters);
console.log(letters);피셔-예이츠 셔플은 배열의 끝에서부터 시작해 각 요소를 아직 결정되지 않은 앞쪽 요소 중 하나와 교환하는 방식으로, O(n)의 시간 복잡도로 균등하게 섞인 결과를 얻을 수 있습니다.