Algorithm/Programmers

Programmers) Lv2 [2020카카오공채] 문자열 압축

삼쓰 웅쓰 2020. 3. 16. 15:24
728x90

출처: https://programmers.co.kr/learn/courses/30/lessons/60057

분류: Lv2 문자열


접근방식

문자열의 길이 N의 절반 이상으로 자르면 압축할 수가 없습니다.
주의 할 개념은 이정도 나머지는 구현이 핵심입니다.

 

카카오 테스트 해설의 출제의도는 다음과 같습니다.

문자열을 다룰 수 있고, 아래 예시와 같이 문자열과 관련된 다양한 작업을 할 수 있는지 파악

  • 문자열 자르기
  • 부분 문자열 얻기
  • 문자열 비교하기
  • 문자열 길이 얻기

 

Swift 는 문자열 다루기가 상대적으로 까다로운데 자유자재로 사용하려면 역시 많은 연습이 필요하겠습니다 😥

 

해결방법

1 ~ N/2 까지 잘라가며 압축 문자열의 길이가 가장 작은 값을 찾아냅니다.

앞에서부터 압축 문자열의 길이로 잘라내면서 중복을 카운트합니다.
카운트가 1이면 중복을 카운트 없이 부분 문자열을 result 에 추가시키고
1 보다 크면 카운트를 앞에 붙여줍니다.

일일이 분기처리를 하기 싫어서 처음에 카운트가 "1" 이여도 일단 다 붙이고
이후에 1을 제거하는 방식도 생각했었는데
이렇게 하면 1000 과 같이 10 이상의 수의 1도 잘라버릴 수가 있습니다 😇

func solution(_ s:String) -> Int {

    var resultCount = s.count

    if s.count == 1 { return 1 }

    for idx in 1...s.count/2 {

        var result = ""
        var indiceStr = ""
        var cnt = 1
        var str = s

        while str.count >= idx {

            let range = str.startIndex..<str.index(str.startIndex, offsetBy: idx)

            if indiceStr == str[range] {
                cnt += 1
            } else {
                if cnt == 1 {
                    result += "\(indiceStr)"
                } else {
                    result += "\(cnt)\(indiceStr)"
                }
                indiceStr = String(str[range])
                cnt = 1
            }
            str.removeFirst(idx)
        }
        if cnt == 1 {
            result += "\(indiceStr)\(str)"
        } else {
            result += "\(cnt)\(indiceStr)\(str)"
        }

        if resultCount > result.count { resultCount = result.count }
    }


    return resultCount
}

 

개선된 코드 

swift에는 기본적으로 문자열을 특정 길이로 자르는 함수는 제공하지 않습니다.
이 부분을 구현하는 부분 때문에 코드가 복잡해질 수 있는데,
문자열을 자르는 부분을 extension으로 처리해주면 보다 깔끔하게 처리할 수 있습니다. 

extension String {
    func split(by length: Int) -> [String] {
        var resultString = [String]()
        
        var string = self
        
        while string.count >= length {
            let range = string.startIndex..<string.index(string.startIndex, offsetBy: length)
            resultString.append(String(string[range]))
            string.removeFirst(length)
        }
        if !string.isEmpty { resultString.append(string) }
        
        return resultString
    }
}

func solution(_ s:String) -> Int {

    if s.count == 1 { return 1 }
    
    var resultCount = s.count

    for idx in 1...s.count/2 {
        
        let truncatedString = s.split(by: idx)

        var duplicatedStr = ""
        var dupCnt = 1
        var result = ""
        
        for str in truncatedString {
            if str == duplicatedStr {
                dupCnt += 1
            } else {
                
                if dupCnt == 1 {
                    result += duplicatedStr
                } else {
                    result += "\(dupCnt)\(duplicatedStr)"
                }
                duplicatedStr = str
                dupCnt = 1
            }
            //print(str, duplicatedStr, dupCnt, result)
        }
        
        if dupCnt > 1 {
            result += "\(dupCnt)\(duplicatedStr)"
        } else {
            result += "\(duplicatedStr)"
        }
        //print(truncatedString, idx, result)
        if resultCount > result.count { resultCount = result.count}
    }
    
    return resultCount

 

여전히 1을 분기처리 하는 부분이 두 번이나 사용되서
깔끔해 보이지 않아 맘에들지 않네요.. 더 좋은 방법이 있을까요 ㅠ-ㅠ

 

배운점

간단한 문제인데 꽤나 오랜 시간이 걸렸다.
문자열은 거의 빼놓지 않고 한 문제씩은 꼭 나오는 데 확실히 연습해서 시간을 단축하자.

역시 코드는 네이밍이 정말 중요하다..
문자열을 다룰 때는 subString이 많이 사용되기 때문에 str, s, 이런 식으로 사용하면
내 코드에 내가 빠져 허우적 허우적....
너무 줄이지 말고 깔끔하고 명확한 네이밍을 하자.
이름을 줄이지 말고 코드를 줄이자 😭😭

extension을 적극적으로 활용하면 더 가독성 높고 깔끔한 코드를 완성시킬 수가 있다.