본문 바로가기
Xcode/Swift - Algorithm

Swift ) 프로그래머스(Lv1) - [1차] 다트 게임 (RegularExpression&Split)

by 후르륵짭짭 2020. 8. 27.
728x90
반응형

programmers.co.kr/learn/courses/30/lessons/17682

 

코딩테스트 연습 - [1차] 다트 게임

 

programmers.co.kr

안녕하세요 후르륵짭짭 입니다.

오늘은 하루에 두개의 글을 남깁니다 ㅎㅎㅎ

(언제 쯤 프로그래머스의 코딩 테스트를 한번 쯤이라도 붙을 수 있을지 ㅠㅠ)

이번 문제는 처음으로 정규표현식을 공부 했고 

다른 사람의 코드를 보면서 새로운 방법 또한 익혔습니다.

 

** 저의 코드 **

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의 모든 문제를 다 풀게 되었습니다~~!!

(제발 그에 상응하는 보답이 꼭 있었으면 좋겠습니다 ㅠㅠ)

 

참고 사이트 : 

baked-corn.tistory.com/136

 

[Swift] Swift에서 정규표현식 사용하기

[Swift] Swift에서 정규표현식 사용하기 저는 지금까지 텍스트 필드 위에 입력되는 사용자의 입력이나 텍스트 덩어리에서 원하는 패턴의 값을 뽑아내거나 검증을 할 때 항상 모든 경우에 대해 if-els

baked-corn.tistory.com

eunjin3786.tistory.com/12

 

[Swift] Swift에서 정규표현식(Regular Expression)을 이용하기

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 extension String{     func getArrayAfterRegex(regex: String) -> [String] {                 do {             let regex =..

eunjin3786.tistory.com

www.nextree.co.kr/p4327/

 

정규표현식(Regular Expression)을 소개합니다.

날이 갈수록 개인정보 보호에 관련하여 보안정책을 점진적으로 강화하고 있습니다. 이에 따라 Web에서 회원가입 시 Password 설정을 복잡해진 보안정책에 맞추다 보니 복잡하게 조합해야만 정상적

www.nextree.co.kr

 

728x90
반응형

댓글