본문 바로가기

Algorithm

[프로그래머스] 1. 신규 아이디 추천 (2021 Kakao Blind Recruitment)

Problem Link : https://programmers.co.kr/learn/courses/30/lessons/72410
Language : Python 3


문자열 (String), 정규식 (regex) 관련 문제인 신규 아이디 추천을 풀어봅시다!

사실 이 문제는 굉장히 친절한 문제입니다. 문제에서 지시한 사항만을 구현해서 풀 수 있는 문제를 자주 출제하지는 않기 때문입니다.

다만 평소에 regex 관련 지식이 있다면 아주 쉽게 풀 수 있겠지만, 그렇지 않다면 일일히 함수를 구현해야 하기에 라이브 코딩이나 시험에서 당황할 수도 있는 문제입니다.

 

(Ref) Python 공식 document 패키지 re 사용법  https://docs.python.org/3/howto/regex.html


문제에서 지시한 사항을 하나씩 살펴봅시다!

1단계 new_id의 모든 대문자를 대응되는 소문자로 치환합니다.

주어진 id 문자열을 변수 new_id로 지정합니다.
(programmers에서는 함수의 입력으로 주어져 있으나 그렇지 않을 경우 input 함수를 사용하여 사용자 입력을 받을 수 있습니다)
string.lower()를 사용하면 주어진 string을 소문자(lower case)로 간단히 변경할 수 있습니다.

new_id = new_id.lower()

2단계 new_id에서 알파벳 소문자, 숫자, 빼기(-), 밑줄(_), 마침표(.)를 제외한 모든 문자를 제거합니다.

re.sub 함수를 사용하여 해결해봅시다.
처음 시도한 방법은 해당 조건을 만족하는 substring만 남기는 방법(re.match)이었는데, 패턴이 여러번 등장하는 경우(e.g. abc!!abc 등)에 대해 해결하지 못했습니다.
저의 짧은 regex 지식으로 새로 시도한 방법은, 지시문을 그대로 따라서 string에서 소문자 / 숫자 / 빼기 / 밑줄 / 마침표를 찾아 모두 '' (empty string)으로 변경하는 방법을 시도했습니다.
regex 표현은 이렇게 짤 수 있습니다.

[ :      문자 class 시작
^ :     부정
a-z :   소문자 범위
0-9 :   숫자 범위
. :       마침표
_ :      밑줄
- :      빼기
] :      문자 class 끝
+ :     패턴이 1 번 이상 등장함

 

"위의 조건을 만족하지 않는 character를 ''(빈 문자열)로 대체한다"는 조건을 sub 함수로 짜보았습니다.

(Ref) [정규식] 핵심만 모아놓은 Cheat Sheet

new_id = re.sub(r"[^a-z0-9._-]+", '', new_id)

3단계 new_id에서 마침표(.)가 2번 이상 연속된 부분을 하나의 마침표(.)로 치환합니다.

2단계를 통과했다면 3단계는 응용하여 해결할 수 있습니다.
"'.'이 1번 이상 등장하는 경우에 '.'로 대체한다"는 조건을 sub 함수로 짜보았습니다.

new_id = re.sub(r"[.]+", '.', new_id)

4단계 new_id에서 마침표(.)가 처음이나 끝에 위치한다면 제거합니다.

이 조건은 re를 사용하는 방법이 바로 떠오르지 않아 제가 아는 가장 간단한 방법인 startswithendswith 함수를 사용했습니다.
조건을 만족하는 경우에 string의 길이는 1 이상이 되므로 다음과 같이 indexing을 해도 list index out of range 에러가 발생하지 않습니다.

new_id = new_id[1:] if new_id.startswith('.') else new_id
new_id = new_id[:-1] if new_id.endswith('.') else new_id

5단계 new_id가 빈 문자열이라면, new_id에 "a"를 대입합니다.

이 조건은 빈 문자열일 경우 len(string) == 0을 만족한다는 점을 이용했습니다.

 Note  빈 문자열은 None이 아닙니다!

new_id = 'a' if len(new_id) == 0 else new_id

6단계 new_id의 길이가 16자 이상이면, new_id의 첫 15개의 문자를 제외한 나머지 문자들을 모두 제거합니다. 만약 제거 후 마침표(.)가 new_id의 끝에 위치한다면 끝에 위치한 마침표(.) 문자를 제거합니다.

길이가 16 이상인 경우 string의 15개의 character만 사용합니다. (15개 character 이외에 나머지를 제거한다는 것과 같은 의미입니다)
Python list indexing에서 :150-14 index를 사용함을 의미합니다. 문자열을 자른 후에는 문자열 중간에 포함되어 있던 마침표가 문자열 끝에 올 수 있습니다. 마침표로 끝나는 string의 경우, -1은 마지막 index를 의미하므로 :-1으로 indexing하여 앞에서부터 마지막 index 전까지의 substring을 사용합니다.

 

 Note 

String의 길이가 15 보다 짧은 경우에는 해당 len(string)을 stop index로 사용합니다 (If i or j is greater than len(s), use len(s)). if len(new_id) >= 16라는 조건을 달아줄 필요가 없다는 의미입니다.

또한, 5단계 조건(빈 문자열에 "a" 대입)과 6단계의 첫번째 조건(길이가 16자 이상일 때 truncate)는 독립적이기 때문에 삼항연산자를 사용하여 한 줄의 코드로 작성할 수 있습니다. String의 길이가 0이면서 동시에 16이 될 수 없기 때문입니다.

(Ref) Built-in Types에서 Python list indexing에 대한 정보를 찾을 수 있습니다. 

new_id = new_id[:15]
new_id = new_id[:-1] if new_id.endswith('.') else new_id

7단계 new_id의 길이가 2자 이하라면, new_id의 마지막 문자를 new_id의 길이가 3이 될 때까지 반복해서 끝에 붙입니다.

이 부분은 재귀를 사용하지 않아도 Python의 string multiplication 기능을 이용하면 보다 쉽게 해결할 수 있습니다.
len(string) <= 2을 만족하는 경우 string의 마지막 character를 길이 3을 만족하기 위해 부족한 개수인 3-len(string) 만큼 반복해주면 됩니다.

new_id = new_id + (new_id[-1]*(3-len(new_id))) if len(new_id) <= 2 else new_id

이제 코드가 완성되었습니다.
휴, 드디어 2021 Kakao Blind Recruitment에서 가장 난이도가 낮은 문제 하나를 해결하였네요!

완성된 코드

def solution(new_id):
    import re
    answer = ''

    # 1. convert to lower case
    new_id = new_id.lower()

    # 2. check valid spell
    new_id = re.sub(r"[^a-z0-9._-]+", '', new_id)

    # 3. compress repeated "."
    new_id = re.sub(r"[.]+", '.', new_id)

    # 4. remove "." at the start and end
    new_id = new_id[1:] if new_id.startswith('.') else new_id
    new_id = new_id[:-1] if new_id.endswith('.') else new_id

    # 5. check empty string and truncate long string 
    new_id = 'a' if len(new_id) == 0 else new_id[:15]

    # 6. remove "." at the end
    new_id = new_id[:-1] if new_id.endswith('.') else new_id

    # 7. add last character for short string
    new_id = new_id + (new_id[-1]*(3-len(new_id))) if len(new_id) <= 2 else new_id

    answer = new_id
    
    return answer
반응형