본문 바로가기
코딩테스트/프로그래머스

프로그래머스 | Python |단순구현 | 신규 아이디 추천 (Lv.1)

by RuntimeSimple 2025. 2. 18.

https://school.programmers.co.kr/learn/courses/30/lessons/72410

 

프로그래머스

SW개발자를 위한 평가, 교육, 채용까지 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프

programmers.co.kr

 

이 문제는 문자열 처리(String Processing) 및 정규 표현식(Regex) 활용이 핵심이다.

주어진 조건에 맞게 7단계 규칙을 순차적으로 적용하여 유효한 아이디를 반환해야 한다.


1. 문제 해결 접근 방법

🔹 7단계 처리 과정

  1. 모든 대문자를 소문자로 변환 → .lower()
  2. 특수 문자 필터링 → 정규 표현식 re.sub()
  3. 연속된 마침표(.)를 하나로 치환 → 정규 표현식 re.sub(r'\\.+', '.', new_id)
  4. 마침표가 처음이나 끝에 위치하면 제거 → .strip('.')
  5. 빈 문자열이면 "a" 대입
  6. 길이가 16자 이상이면 15자까지만 유지 & 끝이 .라면 제거
  7. 길이가 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"
  • #와 @을 제거 (#@는 순서와 관계없이 모두 제거됨).