본문 바로가기

알고리즘문제

[알고리즘] [1차] 다트 게임 (프로그래머스 - 자바스크립트 풀이)

https://school.programmers.co.kr/learn/courses/30/lessons/17682

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

음.. 먼저 점수인 숫자, 한 글자인 알파벳, 스타상인지 아차상인지 특수문자를 나누고,

 

숫자 알파벳 특수문자

1S / 2D* / 3T

 

그 조합을 한 세트로 3개를 분리해야 한다.

정규 표현식을 사용하기로 했다.

 

const reg = /[\d]+[SDT][*#]?/g;

// 역슬래쉬가 원화기호로 나오네..

 

문자 클래스 [ ] 를 사용해서 숫자, 알파벳, 특수문자를 나눴다.

 

[\d]+
[\d] 문자 클래스 안에서의 \d 는 숫자 한 개와 일치한다. d = digit(아라비아 숫자) 이다.
\d 는  [0123456789]  or  [0-9]  와 같은 뜻이다. 참고로 앞·뒤 리터럴 문자 사이에서 쓰는 - 는 범위를 뜻한다.
문자 클래스 뒤에 + 기호는 최소 한 번 반복을 뜻한다.
점수는 0 ~ 10점 까지기 때문에 숫자는 한 자리나 두 자리가 들어간다.
+ 기호 대신에  {1,2} => {최소,최대}  or  {1,} => {최소,} (최소 1번 반복이란 뜻) 을 사용할 수 있다.

 

 

[SDT]
이 정규 표현식은 'S', 'D', 'T' 중 문자 한 개와 일치한다.
기본적으로 대문자, 소문자를 구별하며, 플래그 i 를 추가하면 구분하지 않게 된다.

 

 

[*#]?
이 정규 표현식은 위와 마찬가지로 '*', '#' 중 문자 한 개와 일치한다.
스타상 or 아차상은 있거나 없을 수도 있기 때문에, 문자 클래스 뒤에 ? 기호를 붙였다.
? 기호는 앞의 요소를 최소 0번, 최대 한 번 반복을 뜻한다. 
? 대신에  {0,1}  을 사용할 수 있다.

 

 

이제 만든 정규 표현식을 돌려보면 잘 동작하는걸 알 수 있다.

 

 

 

 

시행 착오

 

처음엔 exec 메소드를 사용해 각 3개의 점수를 나누려 했다.

 

 

첫 번째 시도 점수인 1S 를 의도대로 분리 할 수 있었다.

 

exec 메소드는 배열을 반환하는데, 첫 번째 요소 ( result[0] )에는 일치한 문자열 값이 들어 간다.

 

그래서 반환된 문자열을 score1 변수에 할당해 주었다.

 

 

그리고 첫 번째 점수를 분리 했으니 1S2D*3T 에서 1S 를 제외한 문자열(2D*3T)을 다시 새로운 변수에 담기로 했다.

 

 

이제 2D*3T 에서 두 번째 점수인 2D* 를 분리하기 위해 exec 메소드를 한 번 더 사용했는데

 

 

생각과는 다르게 2D* 이 일치하지 않고 3T 가 일치했다. (참고로 작성한 정규 표현식은 절대로 틀리지 않았다.)

 

인터넷, 책 이것저것 좀 찾아봤지만 결국 왜 2D* 는 건너뛰는지 찾지는 못했다. ㅠ

 

찾게 되면 이유를 추가 내용으로 작성하고자 한다.

 

암튼 이부분에서 의도치않은 문제를 만나고 잠시 문제 풀이가 멈췄었다...

 

 

 

해법 발견

 

그러던 중 문자열 메소드인 'match' 메소드를 보게 되었다.

 

문법

str.match(regexp)
문자열이 정규식과 매치되는 부분을 검색해 준다.
배열로 반환하며 요소에는 정규식과 일치한 값이 들어간다.

 

 

score 변수에 match 메소드를 이용해 정규 표현식을 돌려주면

 

드디어 의도했던 대로 3개의 점수가 각 요소에 차례대로 들어가게 된다.

 

이제 나눠진 점수를 다시 숫자, 알파벳, 특수문자로 나눠야 한다.

 

score 배열을 forEach 메소드로 반복을 돌렸다.

 

score.forEach((value, idx) => {
  // ...
}

 

먼저 숫자는

 

점수 분리용 정규 표현식을 따로 만들어서 match 메소드를 한 번 더 사용했다.

 

value ("1S") 에서 match 메소드로 "1"만 분리한 후 join 메소드를 통해 배열을 문자열로 변환,

 

마지막으로 parseInt 메소드로 정수로 변환한 후  변수 a 에 넣어줬다.

 

제곱을 이용하여 계산해야 하기 때문에 정수로 꼭 변환해야 한다.

 

숫자 분리를 마쳤으니 이제 알파벳을 분리할 차례.

 

숫자 분리때와 마찬가지로

 

알파벳 분리용 정규 표현식을 따로 만들었다.

 

흐름은 숫자를 변수에 할당할 때와 동일 하며,

 

문자열을 그대로 담기 위해 parseInt 메소드만 빠져있다.

 

분리가 됐으면 if 문을 통해서 S, D, T 별로 다른 계산을 해주어야 한다.

 

if (b === "S") {
  answer = a;
} else if (b === "D") {
  answer = Math.pow(a, 2);
} else if (b === "T") {
  answer = Math.pow(a, 3);
}

arr.push(answer);
answer 는 점수 (초기값 : 0), a 는 분리한 숫자, b = 분리한 알파벳

 

S, Single 일때는 점수의 1제곱

 

D, Double 일때는 점수의 2제곱

 

T, Triple 일때는 점수의 3제곱을 해주었다.

 

 

그리고 1차적으로 계산한 점수를 빈 배열 arr 에 넣어 주었다. (스타상, 아차상 계산은 마지막에)

 

1S : 1의 1제곱 = 1

2D : 2의 2제곱 = 4

3T : 3의 3제곱 = 27

 

(forEach 문 안에서 출력했기 때문에 배열처럼 안보이지만 배열이다.)

 

 

이제 마지막 특수문자 분리!

 

역시 특수문자 분리용 정규 표현식을 따로 만들었고

 

변수 c 에 특수문자를 할당해 줬다. 그런데 여기에서는 join 메소드가 빠져있다.

 

왜 배열 => 문자열로 변환하는 작업을 붙이지 않았는가?

 

이유는, 특수문자(스타상 or 아차상)는 있거나 없거나 둘 중 하나인데

 

특수문자가 없는 경우엔 null 이 리턴된다. 때문에 null 에 join 메소드를 붙이면 에러가 발생한다.

 

if (c) {
  c = c.join("");
}

 

따라서 c 가 null 이 아닐 경우에만 join 메소드를 사용하도록 if 문을 추가해 주었다.

 

그리고 스타상(*) 일 때와 아차상(#) 일 때의 계산을 if 문을 통해 추가해 주었다.

 

if (c === "*") {
  arr[idx] *= 2;
  arr[idx - 1] *= 2;
} else if (c === "#") {
  arr[idx] *= -1;
}

 

forEach 문의 파라미터인 idx 를 통해서

 

1차 계산된 점수가 들어있는 arr 배열의 요소에 접근하도록 했다.

 

스타상(*)일 경우, 현재의 배열 요소와 이전 요소에 2배를 해주고

 

아차상(#)일 경우, 현재 요소에 -1을 곱하여 음수로 만들어 준다.

 

마지막으로 reduce 메소드를 사용해서 점수 총합을 리턴해 주었다.

 

return (answer = arr.reduce((prev, curr) => {
  prev += curr;
  return prev;
}, 0));

 

제출한 정답 코드

function solution(dartResult) {
  var answer = 0;
  var arr = [];
  const reg = /[\d]+[SDT][*#]?/g;
  const numberReg = /\d+/g; // 점수 분리
  const timesReg = /[SDT]/g; // Single, Double, Triple 분리
  const awardReg = /[*#]/g; // 스타상, 아차상 분리

  const score = dartResult.match(reg);

  score.forEach((value, idx) => {
    let a = parseInt(value.match(numberReg).join("")); // 숫자 분리하여 a에 할당
    let b = value.match(timesReg).join(""); // S, D, T 분리하여 b에 할당
    let c = value.match(awardReg); // 스타상, 아차상 분리하여 c에 할당

    if (b === "S") {
      answer = a;
    } else if (b === "D") {
      answer = Math.pow(a, 2);
    } else if (b === "T") {
      answer = Math.pow(a, 3);
    }

    if (c) {
      c = c.join("");
    }

    arr.push(answer); // 제곱만 계산한 점수를 할당하고 그 뒤에 스타상, 아차상 계산

    if (c === "*") {
      arr[idx] *= 2;
      arr[idx - 1] *= 2;
    } else if (c === "#") {
      arr[idx] *= -1;
    }
  });

  return (answer = arr.reduce((prev, curr) => {
    prev += curr;
    return prev;
  }, 0));
}

 

문제 풀이를 작성하고 나니 이걸 볼 사람이 있는지 모르겠지만,

이 글을 보고 다른 사람들이 내 코드를 잘 이해할 수 있을까? 문득 궁금해진다..