다트 게임
카카오톡에 뜬 네 번째 별! 심심할 땐? 카카오톡 게임별~
카카오톡 게임별의 하반기 신규 서비스로 다트 게임을 출시하기로 했다. 다트 게임은 다트판에 다트를 세 차례 던져 그 점수의 합계로 실력을 겨루는 게임으로, 모두가 간단히 즐길 수 있다.
갓 입사한 무지는 코딩 실력을 인정받아 게임의 핵심 부분인 점수 계산 로직을 맡게 되었다. 다트 게임의 점수 계산 로직은 아래와 같다.
- 다트 게임은 총 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 |
문제부터 엄청나게 길다. 간단히 요약하자면, 다트게임을 하는데 점수, 배수, 특수 효과로 나누어져 있고, 이를 순서대로 쓴 문자열로부터 점수를 알아내라는 것이었다.
문자 | 기능 |
숫자 | 점수(0~10) |
S | 점수 * 1 |
D | 점수^2 |
T | 점수^3 |
* | 방금 얻은 점수와 직전 얻은 점수에 * 2 |
# | 방금 얻은 점수에 * -1 |
처음에는 for문을 이용해 풀려고 했다. 나는 코딩 초고수가 아니기 때문에 간단하게 해결하기도 어려웠고, 조건도 많아 어쩔 수 없이 코드를 길게 작성할 수 밖에 없었다.
- for문 작성(변수 i), 현재 점수를 담을 score 변수, 누적 점수를 담을 answer 변수 선언
- dartResult의 i번째 문자가 정수라면 score를 0으로 초기화한 후 dartResult의 i번째 문자를 score에 담음
- dartResult의 i번째 문자가 S, D, T라면 score에 각 문자에 맞는 연산 실행 후 answer에 score 더함
- dartResult의 i번째 문자가 *, #라면 answer에 각 문자에 맞는 연산 실행
- 최종 결과 = answer
이 방법을 사용했었는데, 계속 결과가 안나와서 조건을 다시 읽어보니 내가 문제를 잘못 이해하고 있었던 것이었다. 허
잘못 알았던건 *인데, *이 나오면 지금까지 얻은 점수에 2배를 하는건줄 알고 있었지만, 알고보니 이번에 얻은 점수와 바로 전에 얻은 점수 2개에만 2배를 해주는 것이었다.
그렇게 되면 N번째의 score와 N-1번째의 score을 구분할 수 있는 방법이 필요했는데, 이 방법으로는 방금 얻은 점수는 알 수 있지만 직전 얻은 점수는 알 수 없을 것 같았다. 따라서 다른 방법을 사용하기로 했다.
그 다음 생각한 방법은 문자열 dartResult을 [1S, 2D, *, 3T] 등의 배열로 나누어 각 배열의 요소별로 계산한 후 마지막에 *와 #을 적용해 주는 것이었다. 배열을 사용한다면 위에 발생한 문제인 N, N-1번째 점수도 구분 가능하고, 배열에 미리 점수들이 담겨 나오기 때문에 계산도 훨씬 용이할 것 같았다.
- 문자열을 S, D, T, *, # 기준으로 나누어 배열로 만듬
- 각 배열 요소를 연산하여 다트를 한번 던졌을 때의 점수 계산
- 배열에 *이 있다면 * 기준 N-1, N-2번째 요소에 2를 곱함
- #이 있다면 # 기준 N-1번째 요소에 -1을 곱함
- 연산이 완료된 요소들을 모두 더함
우선 S, D, T, *, # 를 기준으로 문자열을 나누어 주어야 했다.
여기선 정규 표현식과 while문을 이용하기로 했다.
- dartResult의 0번째 문자부터 반복 시작
- 반복 횟수(요소 인덱스)를 나타내주는 변수 i와 문자열을 자르기 시작할 인덱스 n을 선언 후 모두 0으로 초기화
- 자른 문자열을 넣을 빈 배열 score 선언
- i번째 문자가 정수라면 통과. i는 1씩 증가
- 정규표현식을 통해 i번째 문자가 S, D, T, *, #라면 n부터 i까지 배열을 잘라 score에 push
- 다음에 자를 문자열은 현재 S, D, T, *, #가 나온 인덱스의 다음 인덱스부터이므로, n에 i+1을 저장
- i가 dartResult의 길이만큼 반복하면 종료
let i = 0
let n = 0
let arr = []
while (i < dartResult.length) {
if (dartResult[i].match(/[DST*#]/)) {
score.push(dartResult.slice(n, i + 1))
n = i + 1
}
i++
}
"1S2D*3T"를 예로 들어보자. 이와 같은 과정을 거치면 배열 score는 ["1S", "2D", "*", "3T"]라는 배열이 된다.
그 다음은 배열의 요소들을 연산해주면 된다.
배열 요소 연산은 정수 부분과 문자 부분으로 나누어 문자의 종류에 따라 정수 부분에 연산을 해주면 된다.
이때 정수는 0~10까지 존재할 수 있기 때문에 score[0][1]로 선언해주었다간 오류가 나게 된다.
만약 score[0]이 "1S"라면 길이는 2, 정수 부분은 1이 되고,
score[0]이 "10S"라면 길이는 3, 정수 부분은 2가 되므로 0부터 score[0]의 길이 - 1까지 slice 해주면 정수 부분만 남게 된다.
이렇게 구한 정수 부분을 num이라는 변수에 저장해준다.
그리고 if-else if문과 includes를 통해 score[0]이 S를 포함한다면 score[0]을 num*1로, D를 포함한다면 Math.pow(num, 2), T를 포함한다면 Math.pow(num, 3)으로 바꾸어 주면 된다. 모든 배열 요소에 실행해야 하므로 for문을 사용한다.
for (let i = 0; i < score.length; i++) {
let num = score[i].slice(0, score[i].length - 1)
if (score[i].includes("S")) {
score[i] = num * 1
} else if (score[i].includes("D")) {
score[i] = Math.pow(num, 2)
} else if (score[i].includes("T")) {
score[i] = Math.pow(num, 3)
}
}
이 과정까지 끝나면 score 배열은 S, D, T 연산이 끝난 정수들과 *, #만 남게 된다.
*과 #의 연산은 배열의 N-1, N-2번째 요소들을 이용해 하는 것이기 때문에 for문을 사용하여 요소에 접근 후 연산을 실행하였다.
score의 길이만큼 for문 실행(변수 = i)
- i번째 요소가 *이라면 i-2, i-1번째 요소에 2를 곱함
- i번째 요소가 #이라면 i-1번째 요소에 -1을 곱함
그런데, 여기서 또 문제가 생겼다. 바로 i-2번째 요소가 정수가 아닌 경우!
"1S*2S*3S"를 예로 들어보면, 위 과정 직전까지 score = [1, "*", 2, "*", 3]인 상태일 것이다.
첫번째 *까지는 정상 작동 하지만, 두번째 *의 경우 i-1은 2지만 i-2는 *이 된다. 이 경우 1에 *이 적용되어야 하는데 *에 *이 적용되어버려 오류가 나게 된다.
따라서 i-2번째 요소까지 따져서 i-2번째 요소가 정수가 아니라면 i-3번째 요소에 2를 곱하도록 하였다.
#의 경우에는 # 앞에 #나 *이 나올 일이 없기 때문에 별다른 조치를 취해주지 않았다.
for (let i = 0; i < score.length; i++) {
if (score[i] == "*") {
if (!Number.isInteger(score[i - 2])) {
score[i - 1] *= 2
score[i - 3] *= 2
}else {
score[i - 1] *= 2
score[i - 2] *= 2
}
} else if (score[i] == "#") {
score[i - 1] *= -1
}
}
마지막은 filter() 함수를 통해 score 배열에서 정수 요소들만 추린 뒤 reduce() 함수로 배열의 요소들을 모두 더해주었다.
function solution(dartResult) {
var answer = 0;
let i = 0
let n = 0
let score = []
while (i < dartResult.length) {
if (dartResult[i].match(/[DST*#]/)) {
score.push(dartResult.slice(n, i + 1))
n = i + 1
}
i++
}
for (let i = 0; i < score.length; i++) {
let num = score[i].slice(0, score[i].length - 1)
if (score[i].includes("S")) {
score[i] = num * 1
} else if (score[i].includes("D")) {
score[i] = Math.pow(num, 2)
} else if (score[i].includes("T")) {
score[i] = Math.pow(num, 3)
}
}
for (let i = 0; i < score.length; i++) {
if (score[i] == "*") {
if (!Number.isInteger(score[i - 2])) {
score[i - 1] *= 2
score[i - 3] *= 2
} else {
score[i - 1] *= 2
score[i - 2] *= 2
}
} else if (score[i] == "#") {
score[i - 1] *= -1
}
}
answer = score.filter(a => Number.isInteger(a)).reduce((a, b) => a + b)
return answer;
}
휴~ 끝!
이 문제가 카카오 코테였다는데......
역시 모두가 가고 싶은 기업은 다르구먼...
'코딩 테스트 풀이 > 프로그래머스' 카테고리의 다른 글
[LV1] 햄버거 만들기 (0) | 2023.01.16 |
---|---|
[LV1] 체육복 (0) | 2023.01.14 |
[LV1] 소수 찾기 (0) | 2023.01.11 |
[LV1] 문자열 내 마음대로 정렬하기 (0) | 2023.01.09 |
[LV1] 행렬의 덧셈 (0) | 2023.01.08 |