Q. 데이터 처리 전문가가 되고 싶은 어피치는 문자열을 압축하는 방법에 대해 공부를 하고 있습니다.
최근에 대량의 데이터 처리를 위한 간단한 비손실 압축 방법에 대해 공부를 하고 있는데, 문자열에서 같은 값이 연속해서 나타나는 것을 그 문자의 개수와 반복되는 값으로 표현하여 더 짧은 문자열로 줄여서 표현하는 알고리즘을 공부하고 있습니다.
간단한 예로 aabbaccc의 경우 2a2ba3c(문자가 반복되지 않아 한번만 나타난 경우 1은 생략함)와 같이 표현할 수 있는데, 이러한 방식은 반복되는 문자가 적은 경우 압축률이 낮다는 단점이 있습니다. 예를 들면, abcabcdede와 같은 문자열은 전혀 압축되지 않습니다. 어피치는 이러한 단점을 해결하기 위해 문자열을 1개 이상의 단위로 잘라서 압축하여 더 짧은 문자열로 표현할 수 있는지 방법을 찾아보려고 합니다.
예를 들어, ababcdcdababcdcd의 경우 문자를 1개 단위로 자르면 전혀 압축되지 않지만, 2개 단위로 잘라서 압축한다면 2ab2cd2ab2cd로 표현할 수 있습니다. 다른 방법으로 8개 단위로 잘라서 압축한다면 2ababcdcd로 표현할 수 있으며, 이때가 가장 짧게 압축하여 표현할 수 있는 방법입니다.
다른 예로, abcabcdede와 같은 경우, 문자를 2개 단위로 잘라서 압축하면 abcabc2de가 되지만, 3개 단위로 자른다면 2abcdede가 되어 3개 단위가 가장 짧은 압축 방법이 됩니다. 이때 3개 단위로 자르고 마지막에 남는 문자열은 그대로 붙여주면 됩니다.
압축할 문자열 input이 매개변수로 주어질 때, 위에 설명한 방법으로 1개 이상 단위로 문자열을 잘라 압축하여 표현한 문자열 중 가장 짧은 것의 길이를 return 하도록 string_compression 함수를 완성해주세요.
- 문자열의 길이는 1 이상 1,000 이하입니다.
- 문자열은 알파벳 소문자로만 이루어져 있습니다.
이 때, 문자열은 항상 제일 앞부터 정해진 길이만큼 잘라야 합니다.
입출력 예 #5 처럼 xababcdcdababcdcd 이 입력되어도,
문자열을 x / ababcdcd / ababcdcd 로 자르는 것은 불가능합니다.
이 경우 어떻게 문자열을 잘라도 압축되지 않으므로 가장 짧은 길이는 17이 됩니다.
"aabbaccc" # -> 7
"ababcdcdababcdcd" # -> 9
"abcabcdede" # -> 8
"abcabcabcabcdededededede" # -> 14
"xababcdcdababcdcd" # -> 17
먼저 어느정도 길이로 쪼갤 수 있는지 부터 생각 했을 때 전체 길이의 절반을 초과하는 길이는 2번 이상이 나올 수 없으므로 쪼개어지는 각 길이는 전체 길이의 절반을 초과하지 못한다. 따라서 먼저 for문을 통해 쪼개어지는 길이 별로 반복되도록 한다. 여기까지만 생각해내고 중간 단계를 생각해내지 못하여 스스로 더 진행하지는 못하였다.
def string_compression(string):
n = len(string)
compression_length_array = []
for split_size in range(1, n // 2 + 1):
내가 놓친 중간는 for문으로 나눠놓은 케이스별로 string에 들어오는 문자열을 배열로 나눠 놓는 것이다. 답안지에서는 splited라는 변수 안에 각 길이 별로 나눠진 문자열의 배열들을 정렬시켜 두었다. 이 부분을 생각해내지 못해 이후의 생각들과 처음의 for문을 연계시키지 못했다.
# splited = []
# for i in range(0, n, split_size):
# splited.append(string[i:i + split_size])
splited = [string[i:i + split_size] for i in range(0, n, split_size)] # 한 줄로 간단하게 출력 가능하다.
이후의 과정은 간단했다. 앞 혹은 뒤의 값과 비교후 같으면 count를 늘려가고, 같지 않다면 count를 1로 초기화 시킴과 동시에 해당하는 count와 문자열을 미리 선언한 copressed_length_array에 차곡차곡 쌓아주면 된다.
compressed = ""
count = 1
for j in range(1, len(splited)):
prev, cur = splited[j - 1], splited[j]
if prev == cur:
count += 1
else:
if count > 1:
compressed += (str(count) + prev)
else:
compressed += prev
count = 1 # 초기화
if count > 1:
compressed += (str(count) + splited[-1])
else:
compressed += splited[-1]
compression_length_array.append(len(compressed))
어려운 개념이나 함수에 대한 이해가 크게 필요하지는 않은 문제여서 문제의 의도에 집중하면 어렵지 않게 접근할 수 있는 문제였다. 다만 접근과 전체적인 흐름은 짤 수 있었지만 세부적으로 섬세하게 코딩하는 부분에서 부족함을 느꼈다. 이건 많이 연습해 보는 수 밖에는 없는 것 같다.
input = "abcabcabcabcdededededede"
def string_compression(string):
n = len(string)
compression_length_array = []
for split_size in range(1, n // 2 + 1):
compressed = ""
# splited = []
# for i in range(0, n, split_size):
# splited.append(string[i:i + split_size])
splited = [string[i:i + split_size] for i in range(0, n, split_size)] # 한 줄로 간단하게 출력 가능하다.
count = 1
for j in range(1, len(splited)):
prev, cur = splited[j - 1], splited[j]
if prev == cur:
count += 1
else:
if count > 1:
compressed += (str(count) + prev)
else:
compressed += prev
count = 1 # 초기화
if count > 1:
compressed += (str(count) + splited[-1])
else:
compressed += splited[-1]
compression_length_array.append(len(compressed))
return min(compression_length_array)
print(string_compression(input))
카카오에 신입 개발자로 입사한 콘은 선배 개발자로부터 개발역량 강화를 위해 다른 개발자가 작성한 소스 코드를 분석하여 문제점을 발견하고 수정하라는 업무 과제를 받았습니다. 소스를 컴파일하여 로그를 보니 대부분 소스 코드 내 작성된 괄호가 개수는 맞지만 짝이 맞지 않은 형태로 작성되어 오류가 나는 것을 알게 되었습니다.
수정해야 할 소스 파일이 너무 많아서 고민하던 콘은 소스 코드에 작성된 모든 괄호를 뽑아서 올바른 순서대로 배치된 괄호 문자열을 알려주는 프로그램을 다음과 같이 개발하려고 합니다.
용어의 정의
'(' 와 ')' 로만 이루어진 문자열이 있을 경우, '(' 의 개수와 ')' 의 개수가 같다면 이를 균형잡힌 괄호 문자열이라고 부릅니다.
그리고 여기에 '('와 ')'의 괄호의 짝도 모두 맞을 경우에는 이를 올바른 괄호 문자열이라고 부릅니다.
예를 들어, "(()))("와 같은 문자열은 균형잡힌 괄호 문자열 이지만 올바른 괄호 문자열은 아닙니다.
반면에 "(())()"와 같은 문자열은 균형잡힌 괄호 문자열 이면서 동시에 올바른 괄호 문자열 입니다.
'(' 와 ')' 로만 이루어진 문자열 w가 균형잡힌 괄호 문자열 이라면 다음과 같은 과정을 통해 올바른 괄호 문자열로 변환할 수 있습니다.
1. 입력이 빈 문자열인 경우, 빈 문자열을 반환합니다.
2. 문자열 w를 두 "균형잡힌 괄호 문자열" u, v로 분리합니다. 단, u는 "균형잡힌 괄호 문자열"로 더 이상 분리할 수 없어야 하며, v는 빈 문자열이 될 수 있습니다.
3. 문자열 u가 "올바른 괄호 문자열" 이라면 문자열 v에 대해 1단계부터 다시 수행합니다.
3-1. 수행한 결과 문자열을 u에 이어 붙인 후 반환합니다.
4. 문자열 u가 "올바른 괄호 문자열"이 아니라면 아래 과정을 수행합니다.
4-1. 빈 문자열에 첫 번째 문자로 '('를 붙입니다.
4-2. 문자열 v에 대해 1단계부터 재귀적으로 수행한 결과 문자열을 이어 붙입니다.
4-3. ')'를 다시 붙입니다.
4-4. u의 첫 번째와 마지막 문자를 제거하고, 나머지 문자열의 괄호 방향을 뒤집어서 뒤에 붙입니다.
4-5. 생성된 문자열을 반환합니다.
균형잡힌 괄호 문자열 p가 매개변수로 주어질 때, 주어진 알고리즘을 수행해 올바른 괄호 문자열로 변환한 결과를 반환하시오.
"(()())()" # -> "(()())()"
")(" # -> "()"
"()))((()" # -> "()(())()"
문자열이 올바른 문자열인지를 확인하고 올바르지 않은 경우 다시 정렬시켜 올바른 문자열을 만들어내는 것이 이 문제의 목표이다. 가장 먼저 해야할 것은 주어진 문자열이 올바른 문자열인지를 판단할 수 있는 메서드를 만드는 것이다. 기존 연습문제에서도 해본 적이 있는 방법으로 그대로 진행하였다.
def is_correct_parentheses(string):
stack = []
for s in string:
if s == "(":
stack.append(s)
elif stack:
stack.pop() # 스택에 아무것도 없을 때는 그냥 넘어가서 len(stack) != 0으로 False를 반환함
return len(stack) == 0
문제가 없다면 바로 리턴하면 되지만 그렇지 않은 경우에는 올바른 문자열로 수정이 필요하다.
def get_correct_parentheses(balanced_parentheses_string):
if is_correct_parentheses(balanced_parentheses_string):
return balanced_parentheses_string
else:
return change_to_correct_parenthesis(balanced_parentheses_string)
수정하는 메서드를 만드는 것에서 조금 복잡하였다. 수정하는 메서드를 포함해서 총 3가지 메서드가 구성이 되어야 한다. 수정 요청을 받는 메서드, 수정을 위해 분리하는 메서드, 모양을 뒤집는 메서드 이렇게 총 3가지 필요하고 분리와 뒤집기는 수정에 포함되어 진행된다. 먼저 is_correct_parentheses에서 올바른 문자열이 아니라고 판명된 문자열의 경우 change_to_correct_parenthesis로 보내 separate_to_u_v에서 u와 v로 분리된 후 v는 문제의 요구사항에 따라 재귀함수로, u는 뒤집기 함수로 수정되어 리턴하게 된다.
이번 문제는 문제에 대부분의 풀이 순서와 방법이 정해져있으므로 그 순서를 어떻게 잘 나누어 풀이하는가가 중요했던 것 같다. 이전에 연습 문제 중에 비슷한 유형의 간단한 문제를 풀어본 경험이 있지만 그것보다는 훨씬 더 많이 나아간 문제였다. 아직 def를 따로 여러번 선언하여 문제를 풀이해야하는 경우는 시도하기가 어려운 것 같다.
from collections import deque
balanced_parentheses_string = "()))((()"
def is_correct_parentheses(string):
stack = []
for s in string:
if s == "(":
stack.append(s)
elif stack:
stack.pop() # 스택에 아무것도 없을 때는 그냥 넘어가서 len(stack) != 0으로 False를 반환함
return len(stack) == 0
def reverse_parenthesis(string):
reversed_string = ""
for char in string:
if char == "(":
reversed_string += ")"
else:
reversed_string += "("
return reversed_string
def separate_to_u_v(string):
queue = deque(string)
left, right = 0, 0
u, v = "", ""
while queue:
char = queue.popleft()
u += char
if char == '(':
left += 1
else:
right += 1
if left == right:
break
v = ''.join(list(queue))
return u, v
def change_to_correct_parenthesis(string):
if string == "":
return ""
u, v = separate_to_u_v(string)
if is_correct_parentheses(u):
return u + change_to_correct_parenthesis(v)
else:
return "(" + change_to_correct_parenthesis(v) + ")" + reverse_parenthesis(u[1:-1])
def get_correct_parentheses(balanced_parentheses_string):
if is_correct_parentheses(balanced_parentheses_string):
return balanced_parentheses_string
else:
return change_to_correct_parenthesis(balanced_parentheses_string)
print(get_correct_parentheses(balanced_parentheses_string)) # "()(())()"가 반환 되어야 합니다!
print("정답 = (((()))) / 현재 풀이 값 = ", get_correct_parentheses(")()()()("))
print("정답 = ()()( / 현재 풀이 값 = ", get_correct_parentheses("))()("))
print("정답 = ((((()())))) / 현재 풀이 값 = ", get_correct_parentheses(')()()()(())('))