다트 게임 - Swift; 객체를 이용한 풀이
by Steady On[프로그래머스 - 다트 게임] - 난이도 Lv. 1
https://school.programmers.co.kr/learn/courses/30/lessons/17682
문제 분석
구해야 하는 것
0~10의 정수와 문자 S, D, T, *, #로 구성된 문자열이 입력될 시 총점수를 반환하는 함수 → return type은 Int
문제에서 주어진 것
- 입력 형식: "점수|보너스|[옵션]"으로 이루어진 문자열 3세트. 예) 1S2D*3T
- 점수는 0에서 10 사이의 정수이다.
- 보너스는 Single(S), Double(D), Triple(T) 중 하나이다.
- 각 영역 당첨 시 점수에서 1제곱, 2제곱, 3제곱 (점수1 , 점수2 , 점수3)으로 계산된다.
- 옵션은 *이나 # 중 하나이며, 없을 수도 있다.
- 스타상(*) 당첨 시 해당 점수와 바로 전에 얻은 점수를 각 2배로 만든다.
- 아차상(#) 당첨 시 해당 점수는 마이너스된다.
- 스타상(*)은 첫 번째 기회에서도 나올 수 있다. 이 경우 첫 번째 스타상(*)의 점수만 2배가 된다.
- 스타상(*)의 효과는 아차상(#)의 효과와 중첩될 수 있다. 이 경우 중첩된 아차상(#)의 점수는 -2배가 된다.
어떻게 풀어볼까?
구현이 필요한 함수
- 입력 값을 "점수|보너스|[옵션]"으로 분리하여 각 기회당의 점수, 보너스, 옵션으로 분리하는 함수
- 각각의 기회에 부여된 옵션에 따라 직전 점수를 두배로 만드는 함수
- 각 기회에서 얻은 점수의 총 합을 리턴하는 함수
객체 구현
1번 함수 구현 시 각 기회의 점수, 보너스, 옵션의 정보를 어떻게 저장하면 좋을까 고민했다. 일단 점수는 정수, 보너스와 옵션은 문자열이라 배열에 담는 것은 안되고, 또 각각의 기회마다 얻은 점수는 별개로 또 저장해야 했어서 "기회"라는 것이 가지고 있는 정보가 많다고 판단해서 하나의 구조체로 구현하는 것으로 설계 했다.
struct Chance {
let point: Int
let bonus: String
let option: String?
}
그런데 잘보면 보너스는 S, D, T 중 하나이고, 옵션도 *이나 # 혹은 none(없음) 중 하나여서 열거형으로 만들면 각 기회별 score를 계산할때 보너스나 옵션에 따라 switch문으로 case 분기를 하기에도 좋을 것으로 판단되었다.
enum Bonus: Character {
case s = "S"
case d = "D"
case t = "T"
}
enum Option: Character {
case star = "*"
case hashtag = "#"
}
그래서 입력값으로 객체를 만들 수 있도록 rawValue를 Character로 설정해서 입력받는 값으로 만들었다.
struct Chance {
let point: Int
let bonus: Bonus
var option: Option?
}
열거형을 반영한 Chance 구조체다. option은 없을 수도 있으므로 옵셔널 타입으로 만들고, 초기값을 nil로 설정해주었다.
그리고 여기서 이 Chance에서 얻은 점수에 대한 정보도 있어야 하므로 연산 프로퍼티로 구현했다.
struct Chance {
let point: Int
let bonus: Bonus
var option: Option?
var isRevision: Bool = false
var score: Int {
var sum = 0
switch bonus {
case .s: sum = point
case .d: sum = point * point
case .t: sum = point * point * point
}
if let option = self.option {
switch option {
case .star: sum *= 2
case .hashtag: sum *= (-1)
}
}
if isRevision { sum *= 2 }
return sum
}
}
bonus에 따라 포인트를 제곱하고, option을 언래핑한 뒤, 언래핑 되면 옵션으로 인한 연산이 되도록 만들어주었다. 또, 바로 다음 기회에서 얻은 점수가 옵션으로 star("*")를 가지고 있다면, score를 두배로 만들어줘야 하므로 점수 보정 여부를 Bool타입으로 가진 isRevision 변수를 추가해주었다.
함수 구현
문자열을 Chance 배열로 변환하여 반환하는 함수; convertStringToChance
func convertStringToChance(_ dartResult: String) -> [Chance] {
let points = dartResult.split { $0.isNumber == false }.compactMap { Int($0) }
let bonusAndOption = dartResult.split { $0.isNumber }
var chances = [Chance]()
for (point, str) in zip(points, bonusAndOption) {
let bonus = Bonus(rawValue: str.first!)!
let option = Option(rawValue: str.last!)
chances.append(Chance(point: point, bonus: bonus, option: option))
}
return chances
}
dartResult를 매개인자로 받아서 숫자와 숫자가 아닌것으로 분리하고, 분리되어진 두 배열을 zip으로 묶어서 Chance 객체를 만들어서 chances 배열에 추가해서 리턴하도록 했다. 이때, option의 경우 들어온 문자열의 last가 보너스의 문자열이라면 rawValue로 Option 객체를 만들었을 때 nil이 될것이다. 그러면, 굳이 문자열의 길이가 1인지 2인지 구별하지 않고도 Chance 인스턴스를 안정적으로 만들 수가 있다.
각각의 기회에 부여된 옵션에 따라 직전 점수를 두배로 만드는 함수; reviceScore
func reviceScore(_ chances: [Chance]) -> [Chance] {
var revicedChances = chances
for index in 1...2 where chances[index].option == .star {
revicedChances[index-1].isRevision = true
}
return revicedChances
}
어차피 chances의 길이가 3이므로 1번째 요소부터 돌면서 옵션이 "*"인 경우 앞 인덱스에 있는 Chance의 isRevision 값을 true로 바꿔주게 하였다.
점수의 총합을 반환하는 함수; solution
func solution(_ dartResult:String) -> Int {
let chances = convertStringToChance(dartResult)
let revicedChances = reviceScore(chances)
return revicedChances.reduce(0) { $0 + $1.score }
}
보정된 점수를 가지고 있는 revicedChances를 돌면서 score의 합을 구해 리턴하도록 했다.
전체 코드
enum Bonus: Character {
case s = "S"
case d = "D"
case t = "T"
}
enum Option: Character {
case star = "*"
case hashtag = "#"
}
struct Chance {
let point: Int
let bonus: Bonus
var option: Option?
var isRevision: Bool = false
var score: Int {
var sum = 0
switch bonus {
case .s: sum = point
case .d: sum = point * point
case .t: sum = point * point * point
}
if let option = self.option {
switch option {
case .star: sum *= 2
case .hashtag: sum *= (-1)
}
}
if isRevision { sum *= 2 }
return sum
}
}
func convertStringToChance(_ dartResult: String) -> [Chance] {
let points = dartResult.split { $0.isNumber == false }.compactMap { Int($0) }
let bonusAndOption = dartResult.split { $0.isNumber }
var chances = [Chance]()
for (point, str) in zip(points, bonusAndOption) {
let bonus = Bonus(rawValue: str.first!)!
let option = Option(rawValue: str.last!)
chances.append(Chance(point: point, bonus: bonus, option: option))
}
return chances
}
func reviceScore(_ chances: [Chance]) -> [Chance] {
var revicedChances = chances
for index in 1...2 where chances[index].option == .star {
revicedChances[index-1].isRevision = true
}
return revicedChances
}
func solution(_ dartResult:String) -> Int {
let chances = convertStringToChance(dartResult)
let revicedChances = reviceScore(chances)
return revicedChances.reduce(0) { $0 + $1.score }
}
마무리
사실 구현코테 문제에 이렇게 객체까지 구현해서 푸는것은 좀 과했을지도 모르지만, 최근에 공부했던 객체지향 프로그래밍이나 생활체조를 연습해보는 좋은 기회였던 것 같다.
'Programming' 카테고리의 다른 글
초심으로 돌아가기) 객체지향 프로그래밍 2: SOLID 원칙과 객체지향 생활체조 (1) | 2023.05.07 |
---|---|
초심으로 돌아가기) 객체지향 프로그래밍1: 특징 (2) | 2023.05.07 |
초심으로 돌아가기) Swift API Design Guidelines 살펴보기 (0) | 2023.05.07 |
블로그의 정보
Roen의 iOS 개발로그
Steady On