문자열이 숫자인지 판별하는 매서드를 만들어보자

게코젤리·2022년 12월 14일
0
post-custom-banner

개요

자바스크립트 기초 실력을 다지기 위해 프로그래머스 문제를 풀고 있다. 자바스크립트 기본 내장 함수 대신 최대한 for문, if문만을 활용해 보는 게 목표인데 그럼에도 문자열을 숫자로 바꿔야 하는 상황에서는 불가피하게 Number(), parseInt()를 쓸 수 밖에 없다고 생각했다.

하지만 선생님은 위 매서드를 사용하지 않아도 숫자인지 판별할 수 있는 방법이 있다고 하셨고 그 방법에 대해 충분히 고민해 보는 것이 이번 숙제가 되었다.

처음엔 부등호를 생각했다.
"1"> 0 은 true
"d"> 0 은 false

판별하려는 숫자의 범위에 음수, 0이 포함되면 문제가 생기지만
최소한 0보다 큰 양수가 맞는지에 대한 판단은 할 수 있다.

하지만 틀렸다.
자동 타입 변환(암묵적 타입 변환)은 사용하지 않는 것이 좋다, 라는 피드백을 받았다.

그럼 뭘까???
"0"부터 "9"까지 하나하나 비교하는 수 밖에 없을까.
일단 해보기로 한다.

숫자인지 확인

function isNumber(str) {

  function numberCheck(value) {
    const numbers = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"];
    
    for (let j = 0; j < numbers.length; j++) {
      if (value === numbers[j]) {
        return true;
      }
    }
    return false;
  }
  
  for (let i = 0; i < str.length; i++) {
    if(!numberCheck(str[i])){
      return false;
    }
  }

  return true;
}

console.log(isNumber("d0")) // -> false
  1. 인자로 받아온 문자열의 길이 만큼 다음 실행을 반복한다.

  2. isNumber 함수 내부에 numberCheck라는 함수를 만들어 배열에 담겨있는 "숫자"와 일치하면 true, 반복문이 끝나도록 일치하는 "숫자"가 없으면 false를 반환하도록 했다.

  3. 그리고 numberCheck의 반환값이 한 번이라도 false가 나오면 isNumber함수는 즉시 false를 반환하고 아니면 false가 한 번도 없으면 true를 반환한다.

원하는 결과가 나왔지만 음수일 경우는 고려하지 못했다. 음수일 경우에도 true가 나오도록 해보자.

음수일 때도 true

function isNumber(str) {
  if (!str && str !== 0) return false;
  
  function numberCheck(value, start = 0) {
    const numbers = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"];
    for (let i = start; i < value.length; i++) {
      let isNum = false;
      for (let j = 0; j < numbers.length; j++) {
        if (value[i] === numbers[j]) {
          isNum = true;
          break;
        }
      }
      if (!isNum) {
        return false;
      }
    }
    return true;
  }

  if (str[0] === "-") {
    return numberCheck(str, 1);
  } else {
    return numberCheck(str);
  }
}
console.log(isNumber("-1")) // true;
console.log(isNumber(1)) // true;

밖에 나와있던 for문을 numberCheck 함수에 넣고 첫글자가 "-"인지 아닌지에 대한 분기를 나누어 numberCheck를 실행했다.

인자에 문자열이 아닌 숫자를 넣을 경우는 아예 고려하지 않았는데 막상 숫자를 넣어도 true로 잘 작동했다. 숫자의 length는 존재하지 않기 때문에 for문이 아예 실행되지 않기 때문!

내친김에 다른 타입(null, undefined)과 빈 문자열("")도 false로 판별하기 위해 if (!str && str !== 0) return false; 를 추가했다.

Boolean이 아닌 숫자를 반환

한 발 더 나아가서 true, false가 아닌 숫자를 직접 반환해보자. 00, 01, 00010 같이 0으로 시작하는 경우가 조금 까다로울 것 같다.
(숫자가 아닌 경우 그대로 false를 반환한다.)

function isNumber(value) {
  if (!value && value !== 0) return false;

  function numberCheck(v, start = 0) {
    const numbers = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"];
    let calc = 0;
    for (let i = start; i < v.length; i++) {
      let digit = v.length - 1 - i;
      let isNum = false;
      
      for (let j = 0; j < numbers.length; j++) {
        if (v[i] === numbers[j]) {
          isNum = true;
          if (digit === 0) {
            calc += j;
          } else {
            calc += j * 10 ** digit;
          }
          break;
        }
      }
      if (!isNum) {
        return false;
      }
    }
    return calc;
  }

  if (value[0] === "-") {
    return numberCheck(value, 1) * -1;
  } else {
    return numberCheck(value);
  }
}
console.log(isNumber("-100"));

calc 변수를 만들어 자릿수마다 값을 산출해 더해주는 방식이다.
1. 먼저 자릿수에 존재하는 숫자의 값을 확인한다.(numbers의 인덱스 값)
2. 확인한 값 * 10 ** digit을 calc에 더한다.
(let digit = number.length - 1 - i)
"1000"일 때 digit은 1000의 자리부터 3, 100의 자리 2, 10의 자리 1, 1의 자리 0.

"01", "0010" 같이 0으로 시작하는 경우가 까다로울 거라 생각했는데 막상 그렇지 않았다. 값이 0이면 calc에 0(0 * 10 ** digit)을 더하기 때문에 따로 조건을 추가하지 않아도 원하는 결과를 얻을 수 있다.

문제는 isNumber()에 인자로 넣은 것이 문자열이 아니고 숫자일 경우였다. 최종적으로 return하는 값이 for문에서 연산을 마치고 나온 calc이란 변수이기 때문에 애초에 숫자인 경우 for문을 돌지 않고 calc의 초기값 0이 return된다.

숫자면 숫자를 그대로 반환

function isNumber(value) {
  if (!value && value !== 0) return false;

  function numberCheck(v, start = 0) {
    const numbers = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"];
    let calc = 0;
    let onCheck = false;

    for (let i = start; i < v.length; i++) {
      onCheck = true;
      let digit = v.length - 1 - i;
      let isNum = false;

      for (let j = 0; j < numbers.length; j++) {
        if (v[i] === numbers[j]) {
          isNum = true;
          calc += j * 10 ** digit;
        }
      }
      if (!isNum) {
        return false;
      }
    }
    if (onCheck) {
      return calc;
    } else {
      return 0 + v;
    }
  }

  if (value[0] === "-") {
    return numberCheck(value, 1) * -1;
  } else {
    return numberCheck(value);
  }
}
console.log(isNumber(-100));

결국 onCheck라는 flag 변수를 만들어야했다.
초기값 false인 onCheck를 for문이 실행될 때 true로 바꾼다.
onCheck가 true 일 때는 숫자로 바꾼 값(calc)을 return하고, onCheck가 false일때는 매개변수 v를 그대로 return하도록 했다.

-0을 넣었을 때 결괏값이 0이 아닌 -0이 되는 것은
return v를 return 0 + v로 바꿔서 간단하게 해결할 수 있었다.

문제는.. 0010을 넣으면 결괏값이 10이 아니라 8이 된다.

알아보니 js의 레거시 문법으로 앞에 0으로 시작하는 숫자는 8진수로 컴파일이 된다고 한다.
(다만 항상 그렇지는 않은 것이 08, 018, 0118은 각각 10진수 그대로 8, 18, 118이 된다)
이건 뭐 어쩔 수 없는 것 같으니 넘어가자.

아무튼 isNumber() 완성.
좀 더러운 것 같지만 일단 구현에 성공한 것으로 만족.

post-custom-banner

0개의 댓글