FrontEnd
Javascript
Diary
ML
CS
Django
Algorithm
AWS
Co-Work
HTML
CSS
Python
React
ReactNative

#55 알고리즘 연습 - 다트게임(Python)

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

다트 게임은 총 3번의 기회로 구성된다.

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

입력 형식

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

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

출력 형식

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

입출력 예제

예제 dartResult answer 설명
1 1S2D*3T 37 11 * 2 + 22 * 2 + 33
2 1D2S#10S 9 12 + 21 * (-1) + 101
3 1D2S0T 3 12 + 21 + 03
4 1S*2T*3S 23 11 * 2 * 2 + 23 * 2 + 31
5 1D#2S*3S 5 12 * (-1) * 2 + 21 * 2 + 31
6 1T2D3D# -4 13 + 22 + 32 * (-1)
7 1D2S3T* 59 12 + 21 * 2 + 33 * 2

문제풀이
다양한 방법으로 생각을 해보았다. 주어지는 문자열에 대해서 식을 세울수 있지않을까, 그럼 공통되는 인수끼리 묶어주면 편하게 계산이 되지않을까? 하지만 이방법은 * 가 들어가면서 앞순서에도 영향을 주므로 적합하지 않아보였다. 따라서 그냥 앞에서 부터 차례대로 체크를 해주기로 했고, 숫자인 경우에는 연산대상자로 지정해주고 뒤따라오는 조건들에 따라 가공해주었다. 그런데 이 과정에서 S,D,T 와 같은 문자들을 Dict 에 넣어놓고 연산을 value값으로 넣어가져올수 없을까 생각을 해보았지만 다른 문제가 있어서 이점 또한 포기하고 정말 앞에서부터 차례대로 연산을 해주었다.

def solution(n):
    ans = []
    flg = 0
      
    for i in n:
        if i.isdigit():
            if flg == 1:
                ans.append(int(str(ans.pop()) + i))
            else:
                ans.append(int(i))
            flg = 1
        else:
            if i == "D":
                ans[-1] = ans[-1]**2
            if i == "T":  
                ans[-1]=ans[-1]**3
            if i =="*":
                ans[-2:] = map(lambda x : x*2, ans[-2:])
            if i == "#":
                ans[-1] = ans[-1]*-1
            flg = 0 

    return sum(ans)
  • 범위가 0~10이므로 10이 오는 경우를 대비해서 flg 를 설정해주었다. 따라서 연속적으로 숫자가 입력되어지면 그 숫자들을 합쳐서 인식되도록했다.
  • list에 넣어서 다뤄주었는데 처음에 pop()append를 이용해서 하다보니 여러개의 Slicing에 번거로움이 있었고, 통일을 해주기위해 list slicing으로 사용하였다.

다중 if문을 써서 코드는 조금 길어진 경향이 있지만 봤을때 직관적이고 복잡도 측면에서도 그렇게 나쁜것같지는 않다.

import re


def solution(dartResult):
    bonus = {'S' : 1, 'D' : 2, 'T' : 3}
    option = {'' : 1, '*' : 2, '#' : -1}
    p = re.compile('(\d+)([SDT])([*#]?)')
    dart = p.findall(dartResult)
    for i in range(len(dart)):
        if dart[i][2] == '*' and i > 0:
            dart[i-1] *= 2
        dart[i] = int(dart[i][0]) ** bonus[dart[i][1]] * option[dart[i][2]]

    answer = sum(dart)
    return answer

위의 코드를 보면 정규표현식을 사용했는데, 그것보다도 개인적으로는 bonus와 option을 dict화 해서 내가 원래 의도한것처럼 가져오고 있는것인데 나는 기존에 저자리에 string으로 된 연산작용을 넣으려고 했는데 단순히 저렇게 숫자만 대입해줘도 내가 if문으로 나누었던 코드들을 어느정도 일반화 해서 작성해줄수 있겠다. 그리고 a = a* -1a*=-1로 작성해줄 수있는것을 알고 있음에도 저당시에는 왜 저렇게 작성했는지 반성하고있다.

🎈 eval

원래는 eval을 사용해서 문자열로 표현된 식을 평가하는 방법도 생각해보았다. 하지만 문자열을 그대로 실행하는만큼 injection에 있어서 취약하다

list comprehension 과 if를 사용하는 방법

list comprehension + if : [i for i in n if i.isdigit()] list comprehension + if-else : [i if i.isdigit() else ” ” for i in n]