ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [알고리즘/자바스크립트] 중복값 없는 랜덤 숫자 추출하는 여러가지 방법 / 로또번호 생성기
    Algorithm 2019. 6. 3. 15:16

    Math.random()

    자바스크립트의 Math.random() 메소드가 뭔지 MDN에서 살펴보면 0(포함)보다 크고 1(불포함)보다 작은 랜덤 숫자를 부동 소수점(floating-point)으로 반환하는 함수라고 되어있다.

     


    여기서 부동 소수점이란 무엇일까?

    컴퓨터에서 실수를 표현하는 것은 정수를 표현하는 것보다 훨씬 복잡하다. 왜냐면 실수 또한 2진수로 표현해야하기 때문이다. 실수로 표현하는 방식은 두 가지가 있는 데, 바로 고정 소수점(fixed point) 방식부동 소수점(floating point) 방식이다. 

     

    실수는 정수 부분과 소수 부분으로 나눠진다. 이 때 실수를 표현하는 가장 간단한 방법은 소수부의 자릿수를 미리 정하여 표현하는 방법이다. 이 것이 고정 소수점 방식이다. 고정 소수점 방식은 정수부와 소수부의 자리수가 작게 제한되어 있어서 크고 정밀한 수를 표현하는 데 한계가 있다.

     

     

    반면에 부동 소수점 방식은 실수를 가수부와 지수부로 나누어 표현한다. 고정 소수점 방식에 비해 표현할 수 있는 숫자의 범위가 매우 커지며, 크고 정밀한 실수도 표현할 수 있다

    현재 대부분의 프로그래밍 언어는 IEEE 754 표준에 정의된 부동 소수점 방식을 따르고 있다. 부동 소수점 방식은 고정 소수점 방식보다 훨씬 더 크고 정밀한 수를 표현할 수 있다는 장점이 있지만 실수를 표현할 때 항상 오차가 존재한다는 단점이 있다.

     

     

    예를 들어 자바스크립트에서 0.2와 0.2를 곱하는 연산 시 0.04라는 정확한 값이 출력되지 않고 0.04000000000000001이 출력되는 것을 확인할 수 있다. 따라서 프로그래밍 언어에서 실수의 표현은 정확한 수가 아닌 그 수에 가장 가까운 근사치를 표현한다는 점을 알고 있어야 한다. 

    (Note that as numbers in JavaScript are IEEE 754 floating point numbers with round-to-nearest-even behavior.)


     

     

    여하튼 이제 이 Math.random()이란 함수를 사용하여 콘솔 창에 5개의 랜덤 숫자를 출력해보려고 한다.

    for (var i = 0; i < 5; i++) {
      console.log(Math.random());
    }

     

    결과를 보면 0을 포함하여 0보다 크고 1보다 작은 임의의 실수값이 생성되는 것을 확인할 수 있다.

    이제 random()으로 생성한 숫자들에 임의의 숫자 5를 곱하고 1을 더해보면 다음과 같은 결과값을 확인할 수 있다.

     

    for (var i = 0; i < 5; i++) {
      console.log(Math.random() * 5 + 1);
    }

     

     

    이제 위 출력값에서 소수점 부분을 제거하면 정수값만 추출할 수 있다.

    아래의 코드는 1부터 5까지의 랜덤 값을 추출하는 코드이다.

    for (var i = 0; i < 5; i++) {
      console.log(Math.floor(Math.random() * 5 + 1));
    }

     


    소수점 이하 값을 처리하는 메소드는 세가지가 있다.

    Math.floor() : 소수점 이하 값을 버림

    Math.ceil() : 소수점 이하 값을 올림

    Math.round() : 소수점 이하 값을 반올림

    Math.floor(1.5); // 1
    Math.floor(1.2); // 2
    Math.round(1.5); // 2

     

     

    이제 임의의 숫자 a부터 b까지 범위의 랜덤값을 생성해보자.

    function randomNum (lower, upper) {
      for(var i=0; i<10; i++) {
        let myRandom = Math.floor(Math.random() * (upper - lower + 1)) + lower;
        console.log(myRandom);
      }
    }
    randomNum (10, 30);

    10부터 30사이의 값이 랜덤으로 추출된다.

     

     

     

    이제 이걸 활용해서 로또번호를 만들어보자. 로또 번호는 1부터 45까지의 숫자 중 6개를 뽑아야 한다. 

    function lottoNum () {
      let lotto = [];
      for(var i=0; i<6; i++) {
        let n = Math.floor(Math.random() * 45) + 1;
        lotto.push(n);
      }
      return lotto;
    }
    lottoNum ();

    6개의 랜덤 숫자를 배열에 넣어서 return하였다.

     

     

    위 코드의 문제점은 중복값이 발생한다는 것이다.

    중복값을 제거하는 데는 여러가지 방법이 있다.

     

     

    function lottoNum () {
      let lotto = [];
      let i = 0;
      while (i < 6) {
        let n = Math.floor(Math.random() * 45) + 1;
        if (! sameNum(n)) {
          lotto.push(n);
          i++;
        }
      }
      function sameNum (n) {
        for (var i = 0; i < lotto.length; i++) {
          if (n === lotto[i]) {
            return true;
          }
        }
        return false;
      }
      return lotto;
    }

    먼저 while문을 사용해서 lotto번호를 추출한다.

    숫자를 랜덤으로 뽑은 후 sameNum() 함수의 파라미터로 넘겨준다.

    sameNum() 함수는 배열의 요소들을 탐색하여 하나라도 같은 값이 있으면 true를 리턴한다.

    같은 숫자가 없으면 false를 리턴한다.

     

    sameNum()의 결과값이 true이면 배열에 숫자를 집어넣고

    i를 증가시킨다. i가 6이 되어 6보다 작지 않으면 반복을 중단하고 배열을 리턴한다.

     

     

    function lottoNum () {
      let lotto = [];
      let i = 0;
      while (i < 6) {
        let n = Math.floor(Math.random() * 45) + 1;
        if (! sameNum(n)) {
          lotto.push(n);
          i++;
        }
      }
      function sameNum (n) {
        return lotto.find((e) => (e === n));
      }
      return lotto;
    }

    똑같은 로직인데 중복값을 판별할 때, find()함수를 사용하였다.

    find 함수는 주어진 조건을 만족하는 첫 번째 요소의 값을 리턴하고

    조건을 만족하는 요소가 없으면 undefined를 리턴한다.

     

    추출한 숫자와 배열의 요소들을 비교하여

    같은 값이 없으면 undefined를 리턴하므로 falsy가 된다.

     

     

    function lottoNum () {
      let lotto = [];
      for(var i=0; i<6; i++) {
        let n = Math.floor(Math.random() * 45) + 1;
        console.log(sameNum(n))
        if (! sameNum(n)) {
          lotto.push(n);
        } else {
          i--;
        }
      }
      function sameNum (n) {
        return lotto.find((e) => (e === n));
      }
      return lotto;
    }

    while문이 아닌 for문을 사용하였다.

    for문을 사용할 때는 i의 값이 하나씩 증가하므로

    중복값이 나타났을 때, i를 1 감소시켜주어야 한다.

     

     

    function lottoNum () {
      let lotto = [];
      let i = 0;
      while (i < 6) {
        let n = Math.floor(Math.random() * 45) + 1;
        if (notSame(n)) {
          lotto.push(n);
          i++;
        }
      }
      function notSame (n) {
        return lotto.every((e) => n !== e);
      }
      return lotto;
    }

    find함수가 아닌 every() 함수를 사용해보았다.

    every() 함수는 배열의 모든 요소가 주어진 조건을 만족하는지 판별하는 함수이다.

    만약에 모든 요소가 조건을 만족하면 true를, 하나라도 만족하지 않으면 false를 리턴한다.

     

    그러므로 숫자가 하나라도 중복되는지 판별하여

    중복되는 값이 하나도 없으면 true를, 반대의 경우엔 false를 리턴한다.

     

     

    function lottoNum () {
      const lotto = [];
      function makeNum () {
        if (lotto.length < 6) {
          let n = Math.floor(Math.random() * 45) + 1;
          if (notSame(n)) {
            lotto.push(n);
          }
          makeNum();
        }
        function notSame (n) {
          return lotto.every((e) => n !== e);
        }
      }
      makeNum();
      return lotto;
    }

    이번엔 재귀함수를 이용하여 만들어보았다. 재귀함수는 함수가 자기자신을 다시 호출하는 형태의 함수이다. 

    lotto 배열의 length가 6보다 작으면 랜덤 숫자를 뽑아내고 makeNum()을 다시 호출한다.

    배열의 길이가 6이 되면 함수 실행이 끝나게 되고, lotto 배열을 return하면 된다.

     

     


     

    function lottoNum (array) {
      if (! array) {
        var array = [];
      }
      let n = Math.floor(Math.random() * 45) + 1;
    
      if (array.length < 6 && array.indexOf(n) < 0) {
        array.push(n);
        return lottoNum(array);
      } else {
        return array;
      }
    }
    
    lottoNum();

     

    예전에 작성한 로또 번호 코드를 훨씬 간결한 재귀로 수정해보았다.

     

    lottoNum 함수는 array를 parameter로 받는다.

    처음 실행 시 parameter로 넘어오는 배열이 없다면 array란 이름으로 새 배열을 만든다.

    랜덤 번호를 뽑은 후 배열의 길이가 6보다 작고 배열에 해당 번호가 없다면

    번호를 push하고 그 push한 배열을 인자로 넘겨 lottoNum() 함수로 재귀 호출한다.

     

    이제 이 함수는 배열의 길이가 6보다 작으면 계속해서 로또번호를 push하고 재귀 호출을 하다가

    array.length가 6이 되면 그 배열이 최종 리턴된다.

     

    반응형

    COMMENT