https://school.programmers.co.kr/learn/courses/30/lessons/72410
프로그래머스
SW개발자를 위한 평가, 교육, 채용까지 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프
programmers.co.kr
이 문제는 문자열 처리(String Processing) 및 정규 표현식(Regex) 활용이 핵심이다.
주어진 조건에 맞게 7단계 규칙을 순차적으로 적용하여 유효한 아이디를 반환해야 한다.
1. 문제 해결 접근 방법
🔹 7단계 처리 과정
- 모든 대문자를 소문자로 변환 → .lower()
- 특수 문자 필터링 → 정규 표현식 re.sub()
- 연속된 마침표(.)를 하나로 치환 → 정규 표현식 re.sub(r'\\.+', '.', new_id)
- 마침표가 처음이나 끝에 위치하면 제거 → .strip('.')
- 빈 문자열이면 "a" 대입
- 길이가 16자 이상이면 15자까지만 유지 & 끝이 .라면 제거
- 길이가 2자 이하라면 마지막 문자를 반복하여 길이 3까지 확장
2. 코드 구현
import re
def solution(new_id):
# 1단계: 모든 대문자를 소문자로 변환
new_id = new_id.lower()
# 2단계: 알파벳 소문자, 숫자, '-', '_', '.' 제외한 문자 제거
new_id = re.sub(r'[^a-z0-9-_.]', '', new_id)
# 3단계: 연속된 마침표(.)를 하나로 치환
new_id = re.sub(r'\\.+', '.', new_id)
# 4단계: 처음이나 끝에 위치한 마침표(.) 제거
new_id = new_id.strip('.')
# 5단계: 빈 문자열이면 "a" 대입
if not new_id:
new_id = "a"
# 6단계: 길이가 16자 이상이면 첫 15개 문자만 유지
new_id = new_id[:15].rstrip('.') # 끝이 '.'이면 제거
# 7단계: 길이가 2자 이하라면 마지막 문자를 반복하여 길이 3까지 확장
while len(new_id) < 3:
new_id += new_id[-1]
return new_id
3. 코드 해설
🔹 1단계: 소문자로 변환
new_id = new_id.lower()
- 모든 문자를 소문자로 변환 (A-Z → a-z)
- 예: "ABC" → "abc"
🔹 2단계: 불필요한 문자 제거
new_id = re.sub(r'[^a-z0-9-_.]', '', new_id)
- 정규 표현식을 활용하여 허용된 문자(소문자, 숫자, ``, _, .)만 남기고 제거.
- [^...]: 대괄호 안의 문자를 제외한 모든 문자 제거.
- 예: "!@BaT#*..y.abcdefghijklm" → "bat..y.abcdefghijklm"
🔹 3단계: 연속된 마침표(.)를 하나로 변환
new_id = re.sub(r'\\.+', '.', new_id)
- \\.+ → 연속된 마침표(.)를 하나의 .으로 변환.
- 예: "bat...y..abc" → "bat.y.abc"
🔹 4단계: 앞뒤 마침표(.) 제거
new_id = new_id.strip('.'
- .strip('.') → 문자열 앞뒤의 마침표 제거.
- 예: ".bat.y." → "bat.y"
🔹 5단계: 빈 문자열이면 "a" 대입
if not new_id:
new_id = "a"
- 문자열이 비어있다면 "a"로 변경.
- 예: "." → "" → "a"
🔹 6단계: 길이 조정 (15자 제한 & 마지막 . 제거)
new_id = new_id[:15].rstrip('.')
- new_id[:15] → 최대 15자까지만 유지.
- .rstrip('.') → 마지막 문자가 .이면 제거.문제의 요구사항
- 아이디의 길이가 16자 이상이면 처음 15개 문자만 유지해야 한다.
- 그러나, 잘린 후에도 마지막 문자가 .이라면 이를 제거해야 한다.
- 앞쪽의 .은 유지해야 하므로 오른쪽(right)만 제거해야 한다.
- rstrip('.')을 사용하는 이유
- 예: "abcdefghijklmn.p" → "abcdefghijklmn." → "abcdefghijklmn"
🔹 7단계: 길이가 2자 이하라면 마지막 문자 반복
while len(new_id) < 3:
new_id += new_id[-1]
- 조건: len(new_id) < 3
- 문자열의 길이가 3보다 작을 때(1 또는 2)만 반복.
- 길이가 3이 되면 즉시 종료.
📌 만약 <= 3을 사용하면, 길이가 3일 때도 실행되어 오류 발생 가능.
- 길이가 3자 미만이면 마지막 문자를 반복해서 추가.
- 예: "z" → "zzz", "ab" → "abb"
4. 실행 예제
print(solution("...!@BaT#*..y.abcdefghijklm")) # "bat.y.abcdefghi"
print(solution("z-+.^.")) # "z--"
print(solution("=.=")) # "aaa"
print(solution("123_.def")) # "123_.def"
print(solution("abcdefghijklmn.p")) # "abcdefghijklmn"
5. 시간 복잡도 분석
단계 연산 내용 시간 복잡도
1단계 | 소문자 변환 | O(N) |
2단계 | 정규 표현식으로 필터링 | O(N) |
3단계 | 연속된 . 치환 | O(N) |
4단계 | 양 끝 . 제거 | O(N) |
5단계 | 빈 문자열 확인 | O(1) |
6단계 | 15자 제한 및 . 제거 | O(1) |
7단계 | 길이가 2자 이하일 경우 확장 | O(1) |
- 최악의 경우 문자열 길이 N=1000일 때, 대부분의 연산이 O(N)으로 동작.
- 전체 시간 복잡도: O(N) → 빠르게 동작 가능.
여기서 잠깐! re.sub()를 활용한 문자열 필터링에 대해 더 알아봅시다.
📌 re.sub()를 활용한 문자열 필터링
re.sub(pattern, replacement, string)은 정규 표현식(Regex)을 활용하여 문자열 내 특정 패턴을 다른 문자열로 치환하는 함수이다.
1. 기본적인 re.sub() 문법
import re
new_string = re.sub(pattern, replacement, string)
- pattern: 정규 표현식 패턴(찾을 문자열)
- replacement: 치환할 문자열
- string: 원본 문자열
- 반환 값: 치환이 완료된 새로운 문자열
2. re.sub(r'[^a-z0-9-_.]', '', new_id)의 의미
new_id = re.sub(r'[^a-z0-9-_.]', '', new_id)
- r'[^a-z0-9-_.]': 정규 표현식 패턴
- '': 빈 문자열로 치환(즉, 삭제)
- new_id: 원본 문자열
3. 정규 표현식 r'[^a-z0-9-_.]' 해석
^ | 부정(NOT) 기호 → 대괄호 [] 안에서 사용되면 해당 문자들을 제외 |
a-z | 소문자 알파벳 (a~z) |
0-9 | 숫자 (0~9) |
-_. | 허용할 특수 문자 (-, _, .) |
📌 결론: 소문자, 숫자, -, _, .을 제외한 모든 문자를 제거.
4. 예제로 확인하기
import re
new_id = "...!@BaT#*..y.abcdefghijklm"
filtered_id = re.sub(r'[^a-z0-9-_.]', '', new_id)
print(filtered_id)
🔹 출력 결과
...bat..y.abcdefghijklm
- "B" → "b" (소문자로 변환, lower())
- "!@#*" → 제거됨 (정규식 패턴에서 제외)
- .은 유지됨
5. re.sub() 활용한 다양한 예제
🔹 (1) 숫자 제거
import re
text = "hello123world456"
result = re.sub(r'[0-9]', '', text)
print(result) # "helloworld"
- [0-9] → 숫자를 찾아서 제거.
🔹 (2) 공백을 _로 변환
import re
text = "hello world python"
result = re.sub(r'\s+', '_', text)
print(result) # "hello_world_python"
- \s의 의미: \s는 다음을 포함하는 모든 공백 문자를 매칭합니다.
- 공백(Space)
- 탭(Tab, \t)
- 개행 문자(Newline, \n)
- 캐리지 리턴(Carriage Return, \r)
- 폼 피드(Form Feed, \f)
- 수직 탭(Vertical Tab, \v)
- \s+는 하나 이상의 공백 문자(연속된 공백 포함)를 찾는 정규 표현식입니다.
- \s+ → 하나 이상의 공백을 _로 치환.
🔹 (3) 연속된 마침표(.)를 하나로 치환
import re
text = "hello...world..python"
result = re.sub(r'\.+', '.', text)
print(result) # "hello.world.python"
- 정규식 패턴 r'\.+'
- \. → .(마침표) 자체를 의미 (정규 표현식에서 .는 모든 문자를 뜻하므로, \를 붙여 . 자체를 의미하도록 함).
- + → "하나 이상 연속된"을 의미 (즉, .이 연속해서 나타날 경우).
- 치환 대상 '.'
- 연속된 .을 하나의 .으로 변환.
여기서 잠깐! strip()을 활용한 문자열 처리에 대해 더 알아봅시다.
strip()은 문자열 양쪽(앞뒤)에 있는 특정 문자를 제거하는 Python의 문자열 메서드이다.
1. strip()의 기본 문법
string.strip([chars])
- chars(선택 사항): 제거할 문자(들)를 지정한다.
- 여러 개의 문자를 넣을 수 있으며, 해당 문자들이 문자열의 앞과 뒤에서만 제거된다.
- 중간에 있는 문자들은 제거되지 않는다.
- 반환값: 앞뒤 특정 문자가 제거된 새로운 문자열을 반환한다.
2. new_id.strip('.')의 의미
new_id = new_id.strip('.')
- 문자열 new_id의 앞과 뒤에 있는 .(마침표)를 제거.
- 중간(.)은 유지됨.
3. 예제로 확인하기
new_id = ".bat.y."
new_id = new_id.strip('.')
print(new_id) # "bat.y"
- 앞에 있는 "." 제거 → bat.y.
- 뒤에 있는 "." 제거 → "bat.y"
4. strip()과 lstrip(), rstrip() 비교
🔹 1) strip() (앞뒤 모두 제거)
text = "..hello world.."
print(text.strip('.'))
# 출력: "hello world"
- 앞뒤 모든 . 제거.
🔹 2) lstrip() (왼쪽만 제거)
text = "..hello world.."
print(text.lstrip('.'))
# 출력: "hello world.."
- 왼쪽의 . 제거.
- 오른쪽 .은 그대로 유지.
🔹 3) rstrip() (오른쪽만 제거)
xt = "..hello world.."
print(text.rstrip('.'))
# 출력: "..hello world"
- 오른쪽의 .만 제거.
- 왼쪽 .은 그대로 유지.
5. strip()을 활용한 다양한 예제
🔹 (1) 공백 제거
text = " python coding "
print(text.strip())
# 출력: "python coding"
- 공백(스페이스, \\n, \\t)을 제거.
🔹 (2) 여러 개의 문자 제거
text = "###python@@@"
print(text.strip("#@"))
# 출력: "python"
- #와 @을 제거 (#@는 순서와 관계없이 모두 제거됨).
'코딩테스트 > 프로그래머스' 카테고리의 다른 글
프로그래머스 | Python | Hash | 성격 유형 검사하기 (Lv.1) (0) | 2025.02.19 |
---|---|
프로그래머스 | Python | 그리디, 단순구현 | 키패드 누르기 (Lv.1) (0) | 2025.02.19 |
프로그래머스 | Python | BruteForce | 바탕화면 정리 (Lv.1) (0) | 2025.02.18 |
프로그래머스 | Python | 단순구현, Hash | 개인정보 수집 유효기간 (Lv.1) (0) | 2025.02.18 |
프로그래머스 | Python | 단순구현, Hash | 달리기 경주 (Lv.1) (0) | 2025.02.18 |