문자열 압축

신연우·2021년 1월 20일
0

알고리즘

목록 보기
13/58
post-thumbnail

프로그래머스 - 문자열 압축

문제 설명

데이터 처리 전문가가 되고 싶은 "어피치"는 문자열을 압축하는 방법에 대해 공부를 하고 있습니다. 최근에 대량의 데이터 처리를 위한 간단한 비손실 압축 방법에 대해 공부를 하고 있는데, 문자열에서 같은 값이 연속해서 나타나는 것을 그 문자의 개수와 반복되는 값으로 표현하여 더 짧은 문자열로 줄여서 표현하는 알고리즘을 공부하고 있습니다.

간단한 예로 "aabbaccc"의 경우 "2a2ba3c"(문자가 반복되지 않아 한번만 나타난 경우 1은 생략함)와 같이 표현할 수 있는데, 이러한 방식은 반복되는 문자가 적은 경우 압축률이 낮다는 단점이 있습니다. 예를 들면, "abcabcdede"와 같은 문자열은 전혀 압축되지 않습니다. "어피치"는 이러한 단점을 해결하기 위해 문자열을 1개 이상의 단위로 잘라서 압축하여 더 짧은 문자열로 표현할 수 있는지 방법을 찾아보려고 합니다.

예를 들어, "ababcdcdababcdcd"의 경우 문자를 1개 단위로 자르면 전혀 압축되지 않지만, 2개 단위로 잘라서 압축한다면 "2ab2cd2ab2cd"로 표현할 수 있습니다. 다른 방법으로 8개 단위로 잘라서 압축한다면 "2ababcdcd"로 표현할 수 있으며, 이때가 가장 짧게 압축하여 표현할 수 있는 방법입니다.

다른 예로, "abcabcdede"와 같은 경우, 문자를 2개 단위로 잘라서 압축하면 "abcabc2de"가 되지만, 3개 단위로 자른다면 "2abcdede"가 되어 3개 단위가 가장 짧은 압축 방법이 됩니다. 이때 3개 단위로 자르고 마지막에 남는 문자열은 그대로 붙여주면 됩니다.

압축할 문자열 s가 매개변수로 주어질 때, 위에 설명한 방법으로 1개 이상 단위로 문자열을 잘라 압축하여 표현한 문자열 중 가장 짧은 것의 길이를 return 하도록 solution 함수를 완성해주세요.

제한 사항

  • s의 길이는 1 이상 1,000 이하입니다.
  • s는 알파벳 소문자로만 이루어져 있습니다.

입출력 예

sresult
"aabbaccc"7
"ababcdcdababcdcd"9
"abcabcdede"8
"abcabcabcabcdededededede"14
"xababcdcdababcdcd"17

풀이

def solution(s):
    shortest_compress_length = len(s)

    if shortest_compress_length < 3:
        print(shortest_compress_length)

    for i in range(1, shortest_compress_length // 2 + 1):
        arr = []

        for j in range(0, len(s), i):
            arr.append(s[j:j + i])

        string = ""
        idx = 0
        while idx < len(arr):
            count = 1
            source = arr[idx]

            for j in range(idx + 1, len(arr)):
                if source != arr[j]:
                    idx = j
                    break
                count += 1
            else:
                idx = len(arr)

            string += f"{count}{source}" if count != 1 else source

        if shortest_compress_length > len(string):
            shortest_compress_length = len(string)

    return shortest_compress_length

해결 과정

  1. 문자열의 길이가 n이라고 할 때, 압축을 위해 최대 몇 개의 단위로 자를 수 있을까?
    문자열을 압축할 때 가장 짧은 길이를 구하기 위해서는 모든 경우를 일일이 다 해보면서 그 개수를 서로 비교하여 가장 짧은 길이의 문자열을 만든 경우를 찾아야 한다. 그렇다면 중요한 것은 길이가 n인 문자열이 있을 때 최대 몇 개의 단위로 자를 수 있느냐는 것이다.

    길이가 n인 문자열의 경우, 최대 (n / 2)개의 단위로 자를 수 있다. 이유는 절반 이상의 단위로 자른다면 서로 반복되는 문자열이 나오지 않아 결국 길이 n을 가지는 원래 문자열이 나오게 된다. 그러므로 1개의 단위로 자르는 경우부터 (n / 2)개의 단위로 자르는 경우까지 압축을 실시했을 때 문자열의 길이를 비교하면 된다.

  2. 문자열의 길이가 1과 2인 경우는 압축을 할 필요가 없다.
    문자열의 길이가 1과 2인 경우는 압축을 하더라도 결국 같은 길이가 나오기 때문에 압축을 진행할 필요가 없다. 그러므로 사전에 길이를 검사해 문자열의 길이가 3 미만인 경우는 해당 길이를 반환하면 된다.

  3. 문자열 분해하기
    분해한 문자열을 저장하고 있어야 압축을 진행할 수 있으므로 우선, 문자열을 분해해야 한다.

    for j in range(0, len(s), i):
        arr.append(s[j:j + i])

    여기서 i는 압축을 할 때 몇 개의 단위로 압축할지 결정한 값을 말한다. 예를 들어 1개 단위로 압축한다고 결정했다면, i의 값은 1이 되는 것이다. 이 방식으로 문자열을 해당 단위로 분할하여 배열에 집어넣는다.

  4. 압축 진행

    while idx < len(arr):
              count = 1
              source = arr[idx]
    
              for j in range(idx + 1, len(arr)):
                  if source != arr[j]:
                      idx = j
                      break
                  count += 1
              else:
                  idx = len(arr)
    
              string += f"{count}{source}" if count != 1 else source

    우선 비교하고자 하는 대상인 arr[idx] 값을 source 변수에 담는다. 이후, 해당 요소 바로 뒤에 있는 요소가 자신과 같은 값인지 검사한다. 같은 값이라면 count의 값을 1 증가시켜 반복되는 개수를 저장하고, 같지 않다면 idx의 값을 해당 인덱스로 변경하고 반복문을 탈출한다.

    또한, 반복문이 break에 의해 중단되지 않고 모두 수행되었다면 배열 내 모든 요소를 검사했다는 뜻이므로 idx의 값을 배열의 길이로 변경한다.

    이후에는 string에 문제에서 요구한대로 이어붙이면 된다.

  5. 각각의 경우별 문자열 길이 비교하기
    압축이 끝났다면 만들어진 string의 길이를 비교하여 이전까지 수행된 압축 중 가장 길이가 짧았던 값보다 현재 만들어진 string의 길이가 짧다면 해당 값을 string의 길이로 변경하면 된다.

다른 사람의 풀이

def solution(s):
    LENGTH = len(s)
    STR, COUNT = 0, 1
    cand = [LENGTH]  # 1~len까지 압축했을때 길이 값

    for size in range(1, LENGTH):
        compressed = ""
        # string 갯수 단위로 쪼개기 *
        splited = [s[i:i+size] for i in range(0, LENGTH, size)]
        stack = [[splited[0], 1]]

        for unit in splited[1:]:
            if stack[-1][STR] != unit:  # 이전 문자와 다르다면
                stack.append([unit, 1])
            else:  # 이전 문자와 다르다면
                stack[-1][COUNT] += 1

        compressed += ('').join([str(cnt) + w if cnt > 1 else w for w, cnt in stack])
        cand.append(len(compressed))

    return min(cand)  # 최솟값 리턴

소스코드 출처

압축을 진행하는 과정에서 stack을 사용하여 해결할 수 있는 풀이다.(근데 문제 풀이를 보면 queue와 비슷하다는 생각이 든다.) 우선, splited에 문자열을 size 값을 단위로 분할한다. 이후 stack에는 splited에서 나온 첫 번째 문자와 1이라는 값을 list 형태로 push한다. 여기서 1은 해당 문자가 반복되는 숫자를 기록한 것이다.

이후 반복문에서는 splited의 두 번째 문자부터 마지막까지 하나씩 가져온다. 이때 가져온 문자가 이전 문자와 다르다면 새로운 문자이므로 [문자, 1]의 형태로 stack에 push한다.

만약 가져온 문자가 이전 문자와 동일하다면, 해당 문자의 반복되는 횟수 값을 1 증가시킨다.

이후 stack에 저장되어 있는 값을 하나씩 가져와서 cnt(반복된 횟수)가 1 초과라면 str(cnt) + w의 형태로, 아니라면 w의 형태로 문자열을 만들어 해당 길이를 cand에 저장한다.

해당 압축 반복이 끝나면 cand에 들어있는 값들 중 가장 작은 값을 반환하면 된다.

profile
남들과 함께하기 위해서는 혼자 나아갈 수 있는 힘이 있어야 한다.

0개의 댓글