정규 표현식 추가문제 - 옹알이 (2) (Python)

 

프로그래머스

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

programmers.co.kr

 

조카가 발음할 수 있는 네 가지 단어의 조합으로 이루어진 문자열을 찾는 문제입니다. 단, 동일한 발음이 연속으로 나오는 경우는 발음할 수 없다는 제약 조건을 정규표현식으로 정밀하게 필터링하는 것이 핵심입니다.


1.  나의 풀이

허용된 단어들로만 구성되었는지 확인하는 패턴과 연속된 발음을 찾아내는 패턴을 분리하여 구현했습니다.

 
import re

def solution(babbling):
    answer = 0
    # 조카가 발음할 수 있는 단어들이 1번 이상 반복되는지 확인하는 패턴
    pattern1 = re.compile(r"(aya|ye|woo|ma)+")
    # 연속된 동일 발음이 존재하는지 확인하는 수동 패턴
    pattern2 = re.compile(r"(ayaaya|yeye|woowoo|mama)")
    # 역참조를 사용한 동일 패턴 : pattern2 = re.compile(r"(aya|ye|woo|ma)\1")
    
    for b in babbling:
        # 문자열 전체가 유효한 단어들로만 구성되어 있는지 확인
        if re.fullmatch(pattern1, b):
            # 문자열 내 어디든 연속된 발음이 포함되어 있지 않은지 확인
            if not re.search(pattern2, b):
                answer += 1
                
    return answer

2.  오늘 배운 점 및 복기 노트

대괄호 []와 소괄호 ()의 차이

정규표현식에서 대괄호 []는 문자 하나하나를 후보로 보는 '문자 집합'을 정의합니다. 예를 들어 [aya]는 'a', 'y', 'a' 중 문자 하나를 선택하는 것이지 'aya'라는 단어를 찾는 것이 아닙니다. 반면 소괄호 ()는 문자열을 하나의 덩어리로 묶는 '그룹'을 의미하며, 파이프 | 연산자와 결합하여 (aya|ye)와 같이 작성해야 단어 단위의 선택(OR)이 가능해집니다.

re.match와 re.search의 결정적 차이

re.match는 문자열의 시작 지점이 패턴과 일치하는지만 검사합니다. 반면 re.search는 문자열 전체를 훑으며 패턴이 단 한 번이라도 등장하는지 확인합니다. "연속된 발음이 포함되면 안 된다"는 조건을 검사할 때 re.match를 사용하면 문자열 중간이나 끝에 숨어있는 연속 발음을 놓칠 수 있으므로, 반드시 re.search를 사용하여 위치와 상관없이 패턴을 찾아내야 합니다.

re.fullmatch의 활용

re.fullmatch는 문자열의 시작부터 끝까지가 패턴과 완벽하게 일치할 때만 매치 객체를 반환합니다. 이는 앵커 기호인 ^와 $를 패턴 앞뒤에 붙인 것과 같은 효과를 주며, "허용된 단어 이외의 다른 불순물이 섞여 있는가"를 판단할 때 매우 유용합니다.

역참조(Backreference)를 이용한 패턴2 최적화

정규표현식의 역참조는 앞에서 매칭된 문자열을 기억했다가 패턴 내에서 다시 호출하여 사용하는 기능입니다. 이는 단순한 패턴 매칭을 넘어 동일한 데이터의 반복을 찾아낼 때 매우 강력한 도구가 됩니다.


1. 원리: 캡처와 재사용

정규표현식에서 소괄호 ()는 캡처 그룹(Capturing Group)을 생성합니다. 엔진은 이 그룹 안에 매칭된 실제 텍스트를 메모리의 특정 번호에 저장합니다.

  • 캡처(Capture): (aya|ye|woo|ma)가 문자열에서 "ye"를 찾아내면, 이 텍스트는 1번 바구니에 담깁니다.
  • 참조(Reference): 패턴에 작성된 \1은 "현재 1번 바구니에 담긴 텍스트를 이 자리에 그대로 대입하라"는 명령입니다.

따라서 (aya|ye|woo|ma)\1은 논리적으로 (찾은 단어) + (방금 찾은 것과 똑같은 단어)를 찾는 식이 됩니다.

2. 작동 과정의 상세 분석

문자열 "yeyema"를 예로 들어 단계별 매칭 과정을 살펴보겠습니다.

  1. 그룹 매칭: 패턴의 앞부분 (aya|ye|woo|ma)가 "yeyema"의 시작 부분에서 "ye"를 매칭합니다.
  2. 데이터 저장: 현재 1번 그룹의 값은 "ye"로 고정됩니다.
  3. 역참조 확인: 다음 패턴인 \1을 처리하기 위해, 현재 위치("ye" 바로 다음)에 1번 그룹의 값인 "ye"가 있는지 확인합니다.
  4. 결과 판정: 하지만 실제 문자열의 다음 부분은 "ma"입니다. "ye"와 "ma"는 일치하지 않으므로, 이 지점에서의 전체 패턴 매칭은 실패합니다.

만약 문자열이 "yeyema"가 아닌 "yeye ma"였다면, 3번 단계에서 "ye"를 확인하여 매칭에 성공했을 것입니다.