본문 바로가기
Algorithm

[프로그래머스] 다트 게임 | 카카오 블라인드 코딩 테스트 | JavaScript

by Vintz 2021. 6. 13.
반응형

다트 게임

문제 설명

카카오톡에 뜬 네 번째 별! 심심할 땐? 카카오톡 게임별~

프로그래머스 - 다트 게임

카카오톡 게임별의 하반기 신규 서비스로 다트 게임을 출시하기로 했다. 다트 게임은 다트판에 다트를 세 차례 던져 그 점수의 합계로 실력을 겨루는 게임으로, 모두가 간단히 즐길 수 있다.
갓 입사한 무지는 코딩 실력을 인정받아 게임의 핵심 부분인 점수 계산 로직을 맡게 되었다. 다트 게임의 점수 계산 로직은 아래와 같다.

  1. 다트 게임은 총 3번의 기회로 구성된다.
  2. 각 기회마다 얻을 수 있는 점수는 0점에서 10점까지이다.
  3. 점수와 함께 Single(S), Double(D), Triple(T) 영역이 존재하고 각 영역 당첨 시 점수에서 1제곱, 2제곱, 3제곱 (점수1 , 점수2 , 점수3 )으로 계산된다.
  4. 옵션으로 스타상(*) , 아차상(#)이 존재하며 스타상(*) 당첨 시 해당 점수와 바로 전에 얻은 점수를 각 2배로 만든다. 아차상(#) 당첨 시 해당 점수는 마이너스된다.
  5. 스타상(*)은 첫 번째 기회에서도 나올 수 있다. 이 경우 첫 번째 스타상(*)의 점수만 2배가 된다. (예제 4번 참고)
  6. 스타상(*)의 효과는 다른 스타상(*)의 효과와 중첩될 수 있다. 이 경우 중첩된 스타상(*) 점수는 4배가 된다. (예제 4번 참고)
  7. 스타상(*)의 효과는 아차상(#)의 효과와 중첩될 수 있다. 이 경우 중첩된 아차상(#)의 점수는 -2배가 된다. (예제 5번 참고)
  8. Single(S), Double(D), Triple(T)은 점수마다 하나씩 존재한다.
  9. 스타상(*), 아차상(#)은 점수마다 둘 중 하나만 존재할 수 있으며, 존재하지 않을 수도 있다.

0~10의 정수와 문자 S, D, T, *, #로 구성된 문자열이 입력될 시 총점수를 반환하는 함수를 작성하라.

입력 형식

"점수|보너스|[옵션]"으로 이루어진 문자열 3세트.
예) 1S2D*3T

  • 점수는 0에서 10 사이의 정수이다.
  • 보너스는 S, D, T 중 하나이다.
  • 옵선은 *이나 # 중 하나이며, 없을 수도 있다.

출력 형식

3번의 기회에서 얻은 점수 합계에 해당하는 정수값을 출력한다.
예) 37

입출력 예제

예제 dartResult answer 설명
1 1S2D*3T 37 1¹ * 2 + 2² * 2 + 3³
2 1D2S#10S 9 1² + 2¹ * (-1) + 10¹
3 1D2S0T 3 1² + 2¹ + 0³
4 1S*2T*3S 23 1¹ * 2 * 2 + 2³ * 2 + 3¹
5 1D#2S*3S 5 1² * (-1) * 2 + 2¹ * 2 + 3¹
6 1T2D3D# -4 1³ + 2² + 3² * (-1)
7 1D2S3T* 59 1² + 2¹ * 2 + 3³ * 2

일단 테스트만 통과해보자!! 

이번 다트 게임은 카카오 블라인드 신입 공채 테스트 문제다. 신입 테스트 문제인 만큼 집중했고 시간 가는 줄 모르고 풀었다. 내 기준 굉장히 어려운 문제였기 때문에 일단 풀리기라도 하면 다행이다라는 심정으로 풀었는데 결국 첫 테스트는 통과했지만 두번째 테스트에서 실패하고 말았다.

function solution(dartResult) {
    let score = dartResult.split("");
    let add = score.map((n, i) => {
        if(score[i] =='1' && score[i+1] =='0') {
            return 10;
        }
        switch(n) {
            case 'S':
                return Math.pow(score[i-1], 1);
            case 'D':
                return Math.pow(score[i-1], 2);
            case 'T':
                return Math.pow(score[i-1], 3);
            case '*':
                return n;
            case '#':
                return n;
            default:
                return 0;
        }
    });

    for(let i = 0; i < add.length; i++) {
        if(add[i] === '*') {
            add[i-1] = add[i-1]*2;
            if(add[i-2] === 0) {
                add[i-3] = add[i-3]*2;
                add[i-4] = add[i-4]*2;
            }
            add[i-2] = add[i-2]*2;
        } else if(add[i] === '#') {
            add[i] = 0;
            add[i-1] = add[i-1] * -1;
        }
    }

    const result = add
        .filter((n) => n !== 0 && Boolean(n) && Number(n))
        .reduce((acc, curr) => acc + curr);

    return result;
}

다트 게임 테스트 결과

채점에서 실패하고 나서 보니 첫 단추부터 단단히 잘못 끼운 것 같았다. 먼저 보너스와 옵션을 분리하지 않고 switch문을 사용하였고 복잡하고 여러 상황을 고려하지 않은 끼워 맞추기식 for문 계산을 했다. 전체적으로 보면 테스트 케이스에 정말로 억지로 끼워 맞추는 식의 알고리즘을 짰다. 전형적인 안좋은 케이스가 아닌가 싶다 😭 결국 해답이 보이지 않아 카카오 테크 블로그의 문제 해설을 천천히 읽어보고 문제의 의도를 파악하려고 노력했다.

정규식을 통한 문자열의 토큰화

문제의 출제 의도는 문자열 처리에 관한 것이었다. 이 문제는 토큰화 및 의미 분석을 통해 쉽게 계산할 수 있다고 나와있다. 여기서 토큰화의 의미를 제대로 알지 못했는데 정규식의 사용을 통해 깨닫게 되었다.

입력 형식을 보면 "점수|보너스|[옵션]"으로 이루어진 문자열 3세트라고 써져있는데 이게 핵심인 것 같다. 이걸 정규 표현식으로 만들면 다음과 같다.

  • 1~2개 사이의 숫자 토큰(10인 경우 2자리)|S나 D나 T중 어떤 문자든 하나|[*나 #이 없거나 있거나]
  • \d{1,2}[SDT][*#]?
  • 정규 표현식 정리

정확히 3세트로 나누어 진다.

이런식으로 3개의 토큰이 생기게 된다. 이게 블로그에서 말한 토큰화가 아닌가 싶다. 구글링을 통해 여러 풀이를 보았지만 표현식으로 푼 문제를 보고 싶었고 결국엔 찾아냈다. 조씨의 개발 블로그

 

나는 이때 머리에 전구가 켜졌다. 💡

function solution(dartResult) {
    let answer = [];
    // "1S2D*3T" -> ["1S","2D*","3T"]
    const dartSet = dartResult.match(/\d{1,2}[SDT][*#]?/g); 
    
    // 다트게임의 기회는 3번
    for(let i = 0; i < 3; i++) {
    	// 3번의 계산에서 토큰화 작업
        let num = dartSet[i].match(/\d{1,2}/g);
        const bonus = dartSet[i].match(/[SDT]/g);
        const option = dartSet[i].match(/[*#]/g);
        
        // 'S','D','T'는 string, bonus는 object라서 일치 연산자(===) 사용X
        if(bonus == 'S') {
            num = parseInt(num);
        } else if (bonus == 'D') {
            num = num * num;
        } else if (bonus == 'T') {
            num = num * num * num;
        }
        
        if(option == '*') {
            num = num * 2;
            if(i != 0) {
            	// 숫자가 하나 이상이면 그 전의 숫자도 2배
                answer[i-1] = answer[i-1] * 2;
            }
        } else if(option == '#') {
            num = num * (-1);
        }
        answer.push(num);
    }
    
    return answer.reduce((acc, curr) => acc + curr);
}

for문 안에 num의 값만 변하기 때문에 좀 더 알기 쉽게 const와 let으로 선언 해주었다.

정말 깔끔하고 가독성도 좋다. 정규식으로 토큰화 처리를 하니 코드 수도 적어지고 계산도 쉬워지고 빨라졌다. 또한 게임의 기회가 늘어나거나 추가적인 옵션도 넣기에 어려움이 없다. 개발 유지보수성도 좋다. 👏 

이번엔 정규 표현식으로 토큰화 처리를 하는 것에 대해 배웠다. 정말 다양한 곳에 표현식을 쓸 수 있는 것 같다.

 

다른 블로그 풀이들을 보면 난이도가 쉬웠다고도 하고 문제 난이도도 레벨1로 되어있다..하지만 이 문제를 푸는데 거의 7시간이 넘게 걸렸다. 12시에 시작해서 저녁 8시 반까지 앉아있었고 시간가는 줄 모르고 몰입해서 풀었다. 재미있었고 몰입을 몸소 느꼈었다.

한단계씩 조금씩 나아가다 보면 나도 곧 이런 문제들이 쉽게 느껴질 날이 올거라고 생각한다.

 

 

반응형