본문 바로가기

TIL/자바스크립트

배열 섞기, 피셔-예이츠 셔플 ft. 숫자야구게임 [자바스크립트]

여전히 숫자야구게임 만드는 중.

 

다른 숫자 세 개로 이뤄진 세 자리 숫자를 생성해야 함.

 

Math.floor(Math.random()*1000);

처음에 이렇게 시도해 봤으나 숫자가 중복되고 두 자리 숫자도 나와서 다른 방법 찾기로.

 

이 방법 저 방법 시도하고 찾아보다가 스택오버플로우에서 배열 순서를 무작위로 섞는 함수를 찾아냄.

피셔-예이츠 셔플이라고 함. 음악앱이 노래를 무작위한 순서로 재생하도록 만들 때 사용하는 방법이라고.

 

직접 만들어내진 못했으니 코드를 들고 와서 어떻게 배열을 섞는지 분석이나 해보려 함.

 

function shuffle(array) {
  var currentIndex = array.length, temporaryValue, randomIndex;

  while (0 !== currentIndex) {
    randomIndex = Math.floor(Math.random() * currentIndex);
    currentIndex -= 1;

    temporaryValue = array[currentIndex];
    array[currentIndex] = array[randomIndex];
    array[randomIndex] = temporaryValue;
  }

  return array;
}

변수는 currentIndex와 temporaryValue, randomIndex 세 개. currentIndex는 최초에 매개변수인 배열의 길이로 설정.

 

while 구문으로 currentIndex가 0일 아닐 때, 즉 배열의 첫 번째 요소를 뽑지 않은 한 코드블럭을 계속 실행. (뭔가를 반복할 때 지금까지 for loop을 주로 사용했는데 while도 한 번씩 생각해봐야겠다.)

 

Math.random()은 0 이상 1 미만인 숫자를 출력하므로 Math.floor(Math.random() * currentIndex);는 0 이상 currentIndex 미만인 값, 즉 아무 인덱스 출력. 이 값을 randomIndex에 담고 currentIndex -1.

 

temporaryValue에 array[currentIndex], 즉 배열의 마지막 요소를 담음. 그리고 배열 제일 끝에 array[randomIndex], 즉 조금 전에 뽑은 아무 값을 넣음. 아무 값이 있던 자리에 다시 temporaryValue를 담음. => 아무 자리의 값과 가장 뒷자리 값을 맞바꿈.

 

뒤로 밀린 값을 제외하고 이 과정을 반복.

 

피셔-예이츠 셔플을 더 자세하게 설명하고 시각적 자료까지 제공하는 사이트: https://bost.ocks.org/mike/shuffle/


어쨌든 나는 0에서 9 사이 숫자 가운데 무작위로 세 개를 뽑아야 함. 

 

처음 생각한 방법은 0~9로 이뤄진 배열을 피셔-예이츠 셔플에 넣은 뒤 slice로 뒤쪽은 잘라내기.

var randomNum;

function create() {
  var arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

    function shuffle(array) {
      var currentIndex = array.length, temporaryValue, randomIndex;

      while (0 !== currentIndex) {
        randomIndex = Math.floor(Math.random() * currentIndex); currentIndex -= 1;

        temporaryValue = array[currentIndex];
        array[currentIndex] = array[randomIndex];
        array[randomIndex] = temporaryValue;
      }
        
      return array;
    }
    
  randomNum = shuffle(arr).slice(0, 3);
  return randomNum;
}

randomNum = create;

 

그런데 생각해보니 비효율적. 셔플을 끝까지 진행할 필요가 없고 while 안의 코드블럭을 세 번만 돌린 다음 배열의 가장 뒤에 있는 요소 세 개만 내보내면 됨.

function create() {
  var arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

    function shuffle(array) {
      var currentIndex, temporaryValue, randomIndex;

      for (currentIndex = array.length - 1; currentIndex >= array.length - 3; currentIndex--) {

        randomIndex = Math.floor(Math.random() * currentIndex);

        temporaryValue = array[currentIndex];
        array[currentIndex] = array[randomIndex];
        array[randomIndex] = temporaryValue;

      }

      return array;
    }
    
  randomNum = shuffle(arr).slice(-3);
  return randomNum;
}

randomNum = create;

 

아무 인덱스를 뽑은 다음에 빈 배열에 push하는 방법도 있을 듯.