Computer >> 컴퓨터 >  >> 프로그래밍 >> JavaScript

JavaScript Array.sort() 메서드로 배열 셔플이 가능할까요?

결론부터 말하자면, Array.sort() 메서드를 활용해 배열을 섞는(셔플) 것은 기술적으로 가능하지만, 결과의 무작위성이 보장되지 않아 권장되지 않습니다. 그 이유와 올바른 대안을 함께 살펴보겠습니다.

sort()를 이용한 흔한 셔플 방식과 그 문제점

많은 개발자가 아래처럼 sort()에 랜덤 비교 함수를 넣어 배열을 섞곤 합니다.

const shuffled = arr.sort(() => Math.random() - 0.5);

간단해 보이지만 이 방법에는 두 가지 큰 문제가 있습니다.

1. 편향된 결과: 자바스크립트의 정렬 알고리즘(대부분 V8 엔진의 TimSort)은 요소 간 비교 결과가 일관되지 않으면 예측 불가능하게 동작합니다. 그 결과 특정 순서가 다른 순서보다 더 자주 나타나는 편향(bias)이 발생합니다.

2. 엔진 의존성: 내부 정렬 알고리즘은 브라우저나 자바스크립트 엔진마다 다르므로, 같은 코드라도 환경에 따라 셔플 결과의 분포가 달라질 수 있습니다.

권장 방식: 피셔-예이츠(Fisher-Yates) 셔플

모든 순열이 동일한 확률로 나타나는 균등한 셔플이 필요하다면, 피셔-예이츠 알고리즘을 사용하는 것이 표준적인 해결책입니다. 아래는 실제 구현 예시입니다.

예제

function shuffleDisplay(arr) {
    var tmp, current;
 
    // 배열 길이 계산
    var top = arr.length;
 
    if(top) while(--top) {
        current = Math.floor(Math.random() * (top + 1));
        tmp = arr[current];
        arr[current] = arr[top];
        arr[top] = tmp;
    }
    return arr;
}

사용 예시

const numbers = [1, 2, 3, 4, 5];
console.log(shuffleDisplay(numbers));
// 실행할 때마다 다른 순서로 출력됩니다. 예: [3, 1, 5, 2, 4]

동작 원리

피셔-예이츠 셔플은 배열의 마지막 요소부터 시작해, 아직 섞이지 않은 앞부분 중에서 무작위 인덱스를 하나 선택하고 해당 요소와 자리를 교환(swap)하는 방식으로 동작합니다. 이 과정을 반복하면 시간 복잡도 O(n) 안에 모든 순열이 균등한 확률로 생성됩니다.

정리

  • sort(() => Math.random() - 0.5) 방식은 구현이 간단하지만 편향된 결과를 낼 수 있습니다.
  • 카드 게임, 퀴즈 순서 섞기 등 공정한 무작위성이 필요하다면 피셔-예이츠 셔플을 사용하세요.
  • 원본 배열을 유지하고 싶다면 [...arr]처럼 복사본을 만든 뒤 셔플하는 것이 좋습니다.