programmers.co.kr/learn/courses/30/lessons/17682
안녕하세요 후르륵짭짭 입니다.
오늘은 하루에 두개의 글을 남깁니다 ㅎㅎㅎ
(언제 쯤 프로그래머스의 코딩 테스트를 한번 쯤이라도 붙을 수 있을지 ㅠㅠ)
이번 문제는 처음으로 정규표현식을 공부 했고
다른 사람의 코드를 보면서 새로운 방법 또한 익혔습니다.
** 저의 코드 **
func solution(_ dartResult:String) -> Int {
var array : [Int] = Array(repeating: 0, count: 3)
let list = returnStringByRex(pattern: "[0-9]+[a-z][*#]?", word: dartResult)
var current = 0
for element in list {
let score = returnStringByRex(pattern: "[0-9]+", word: element)
let bonus = returnStringByRex(pattern: "[a-z]", word: element)
let option = returnStringByRex(pattern: "[*#]", word: element)
var total = 0
// print(score.first! , bonus.first! , option.first ?? nil)
switch bonus.first {
case "S":
total = Int(score.first!)!
case "D":
total = Int(pow(Double(score.first!)! , 2))
case "T":
total = Int(pow(Double(score.first!)! , 3))
default:
print("Not happen")
}
if option.count == 1 {
if current == 0 && option.first! == "*" {
total = total * 2
}
else if current > 0 && option.first! == "*" {
total = total * 2
array[current - 1] = array[current - 1] * 2
}
else{
total = total * -1
}
}
array[current] = total
current += 1
}
return array.reduce(0, +)
}
func returnStringByRex(pattern : String , word : String) -> [String] {
do {
let regex = try NSRegularExpression(pattern: pattern, options: .caseInsensitive)
let result = regex.matches(in: word, options: [], range: NSRange(location: 0, length: word.count))
let rexStrings = result.map { (element) -> String in
let range = Range(element.range, in: word)!
return String(word[range])
}
return rexStrings
} catch let error {
print(error.localizedDescription)
}
return []
}
정규 표현식으로 풀려 하니,,, 어려웠습니다.
그런데 정규 표현식을 알아두는 것은 정말 필요하고 좋은 공부하는 계기가 되었습니다.
그럼 하나씩 알아가 보도록 하겠습니다.
** 정규표현식 **
Swift에서 정규 표현식을 만들기 위해서는 NSRegularExpression을 사용해야합니다.
NSRegularExpression은 object-c에서 사용하는 것이라,,, 다소 익히는데 어려움이 있었습니다.
func returnStringByRex(pattern : String , word : String) -> [String] {
do {
let regex = try NSRegularExpression(pattern: pattern, options: .caseInsensitive)
let result = regex.matches(in: word, options: [], range: NSRange(location: 0, length: word.count))
let rexStrings = result.map { (element) -> String in
let range = Range(element.range, in: word)!
return String(word[range])
}
return rexStrings
} catch let error {
print(error.localizedDescription)
}
return []
}
이 코드를 보면 복잡하게 되어 있습니다.
pattern에는 정규표현식의 패턴이 들어가고 word에는 우리가 파싱할 문자가 들어 갑니다.
do {
let regex = try NSRegularExpression(pattern: pattern, options: .caseInsensitive)
}
catch{
}
정규 표현식 NSRegularExpression 타입은 try ~ catch 문으로 감싸주셔야 합니다.
그리고 매개변수 값으로 patter 과 option이 들어가는데, pattern 에는 정규표현식이 들어가고
option에는 기본값으로 대소문자 구분이 되어 있는데, 이렇게 추가로 기능이 들어갈 수도 있습니다.
( 여기서 .caseInsensitive 는 대소문자 상관없이 구분하지 않는 기능 입니다. )
let result = regex.matches(in: word, options: [], range: NSRange(location: 0, length: word.count))
이제 정규식을 가지고 문자를 파싱하도록 하겠습니다.
우리가 만들었던 NSRegularExpression 타입인 regex에 matches를 넣어주시기 바랍니다.
matches는 (in : 파싱할 문자 , options : 옵션 , range : NSRange 타입으로 범위) 를 넣어주면 그에 해당하는 문자열 배열을 제공해줍니다.
(matches 외에도 첫번째 것만 반환해주는 firstmatch 등 여러가지가 있지만 matches를 주로 사용합니다.)
let rexStrings = result.map { (element) -> String in
let range = Range(element.range, in: word)!
return String(word[range])
}
우리가 아무리 파싱을 했더라도 result 타입은 NSTextCheckingResult 입니다...
이것은 우리가 정규식으로 파싱 했을 때, 정규표현식의 text 문자열 입니다. 따라서 이 부분에 변화를 줘야합니다.
따라서 고차함수 map을 이용해서 타입에 변화를 주는데 Range(요소의 범위 , in: 문자열)! 이렇게 해줍니다.
따라서 그 문자열에 해당하는 범위로 String.Range 타입으로 반환이 됩니다.
그래서 word[range]를 통해서 문자열을 파싱 할 수 있게 됩니다.
** 정규표현식 기능 **
이제 정규 표현식 기능을 알아보겠습니다.
- ^ 와 $ -
//^은 문자열이 시작할 때 그 문자로 시작하는지 안하는지 체크하는 것이다.
print(returnStringByRex(pattern: "^Hello", word: "Hello evey Body"))
//Hello
print(returnStringByRex(pattern: "^Hello", word: "Hell evey Body"))
//[]
//$는 문자열이 끝날 때 그 문자로 끝나는건지 확인하는 것이다.
print(returnStringByRex(pattern: "Hello$", word: "Hello evey Body"))
//[]
print(returnStringByRex(pattern: "Hello$", word: "Hello evey Body Hello"))
//Hello
- ... 과 .{} -
//... 이것은 갯수 만큼 보여주는 것 입니다. 다른 표현으로 .{3} 이렇게 가능합니다.
print(returnStringByRex(pattern: "...", word: "Hello evey Body"))
//["Hel", "lo ", "eve", "y B", "ody"]
print(returnStringByRex(pattern: ".{3}", word: "Hello evey Body"))
//["Hel", "lo ", "eve", "y B", "ody"]
- [] 와 [^] -
//[]은 한 문자만 의미한다. 따라서 a-z 의 한 문자를 의미한다. [^]은 그외의 것을 의미한다.
print(returnStringByRex(pattern: "[a-h]", word: "Hello evey Body"))
//["H", "e", "e", "e", "B", "d"]
print(returnStringByRex(pattern: "[^a-h]", word: "Hello evey Body"))
//["l", "l", "o", " ", "v", "y", " ", "o", "y"]
- * 와 + 와 ? -
//*은 e* 라고 할때, e가 연속적으로 0개 이상 여러개 까지 뽑아온다. 하지만 e가 아닌 위치일 경우에는 공백으로 만든다.
print(returnStringByRex(pattern: "e*", word: "Hello evey Body"))
//["", "e", "", "", "", "", "e", "", "e", "", "", "", "", "", "", ""]
print(returnStringByRex(pattern: "e*l", word: "Hello evey Body"))
//["el", "l"]
//+은 e+ 라고 할때, e가 연속적으로 한개 이상 여러개 까지 뽑아온다. 따라서 존재하지 않다면 뽑아오지 않아 공백을 반환 안한다.
print(returnStringByRex(pattern: "e+", word: "Hello evey Body"))
//["e", "e", "e"]
print(returnStringByRex(pattern: "e+v", word: "Hello evey Body"))
//["ev"]
//?는 e? 라고 할때, e가 없으면 공백으로,,, 있으면 그 문자 하나만 가져온다. 즉, 0 ~ 1 를 의미한다.
print(returnStringByRex(pattern: "e?", word: "Hello evey Body"))
//["", "e", "", "", "", "", "e", "", "e", "", "", "", "", "", "", ""]
print(returnStringByRex(pattern: "e?l", word: "Hello evey Body"))
//["el", "l"]
//그런데 만약 e*K 나 e+k 등 하나의 문자만 사용 된다면 그 조건만 되는 것을 반환한다. 각 예시의 두번째 처럼
- () 와 | -
// ()은 그룹화를 의미합니다. 따라서 묶어서 탐색을 할 때 사용 하고
// | 은 OR 기능을 담당합니다. 따라서 이거 아니면 이거 할 때 사용하고
둘이 혼합해서 사용한다면 아래처럼 사용이 가능 합니다.
print(returnStringByRex(pattern: "(Hello|evey)", word: "Hello evey Body"))
//["Hello", "evey"]
print(returnStringByRex(pattern: "([h]|[b])", word: "Hello evey Body"))
//["H", "B"]
** 다른 사람의 코드 **
저의 코드는 굉장히 복잡하지만 공부할게 넘쳐나는 코드였다면
이분의 코드는 정말 간단하고 좋았습니다.
또한 알지 못했던 방식을 새롭게 알 수 있었습니다.
( JongHun Cha님의 코드 입니다.)
func solution(_ dartResult:String) -> Int {
let numberList = dartResult.split(whereSeparator: {$0.isLetter || $0 == "#" || $0 == "*"})
let letterList = dartResult.split(whereSeparator: {$0.isNumber})
var totalScore = 0
for (i, (number, letter)) in zip(numberList, letterList).enumerated() {
var score = 0
if let number = Int(number) {
score = letter.contains("D") ? number * number : letter.contains("T") ? number * number * number : number
}
if letter.contains("*") {
score *= 2
} else if letter.contains("#") {
score = -score
}
if i != 2 {
if letterList[i + 1].contains("*") {
score *= 2
}
}
totalScore += score
}
return totalScore
}
이분의 코드에서 놀랐던 것은 아래 코드 입니다.
let numberList = dartResult.split(whereSeparator: {$0.isLetter || $0 == "#" || $0 == "*"})
//["1", "2", "0"]
let letterList = dartResult.split(whereSeparator: {$0.isNumber})
//["S", "D*", "T"]
//split(whereSeparator : ) 는 클로저 형식으로 여러개의 문자를 기준으로 나눠 줍니다.
//즉 아래 것을 본다면 문자 나 # 또는 *을 제외 한 것을 반환 해준다.
따라서 위에에서는 문자나 #이나 * 을 만나면 나눠주고
아래것은 숫자를 만나면 나눠주는 것 입니다.
또한 삼항 연산자를 저렇게도 사용 할 수 있다는 것을 처음 알았습니다.
score = letter.contains("D") ? number * number : letter.contains("T") ? number * number * number : number
어렵진 않았지만, 굉장히 다양한 방법이 존재했고
배울게 많았던 문제였습니다.
그리고 프로그래머스 Lv1의 모든 문제를 다 풀게 되었습니다~~!!
(제발 그에 상응하는 보답이 꼭 있었으면 좋겠습니다 ㅠㅠ)
참고 사이트 :
'Xcode > Swift - Algorithm' 카테고리의 다른 글
Swift ) 프로그래머스(Lv2) - 문자열 압축 (String) (0) | 2020.08.28 |
---|---|
Swift) LeetCode(Easy) - Valid Palindrome II (String) (0) | 2020.08.27 |
Swift) 프로그래머스(Lv1)- 실패율 (Sorted) (0) | 2020.08.26 |
Swift) 프로그래머스(Lv1) - [1차] 비밀지도 (String) (0) | 2020.08.23 |
Swift) LeetCode(Easy) - Word-Pattern (Hash&String) (0) | 2020.08.20 |
댓글