프로그래머스 [1차] 다트 게임

대부분의 문제상황의 도입부가 그러하듯 나는 극심한 고민에 빠져있었다.
알고리즘 문제를 푸는데 로직은 완성이 되었으나... 문자열 처리에 애를 먹고있었다.
입력은 1S2D*3T, 1D2S#10S, 1D2S0T와 같은 3개의 점수 영역으로 구분되고 점수와 함께 S, D, T 영역, 옵션으로 *, #까지 주어졌다.
평상시라면 매 문자마다 반복문을 돌려서 숫자 찾고, 문자를 찾고, 옵션을 찾았겠지만, 언젠가 똑같은 고생을 하고있을 모습이 눈에 선해 정규식을 정리한다.
많은 사람들이 알고리즘 문제 중에서도 문자열 문제를 까다로워하곤 한다. 복잡한 조건, 예외 처리, 인덱스 관리까지... for문과 if-else가 뒤섞이는 코드는 금방 지저분해지고 스스로도 이해하기 어려워진다.
하지만 정규 표현식(Regular Expression, Regex)을 사용한다면 이런 문자열 문제를 우아하게 해결할 수 있다.
정규식은 문자열에서 특정 패턴을 찾거나, 바꾸거나, 추출하기 위해 사용하는 '패턴 명세 언어'이다. 한번 익혀두면 문자열 문제 해결의 난이도를 낮춰주는 무기가 될 수 있다.
| 메타문자 | 의미 | 예시 | 설명 |
|---|---|---|---|
. | 임의의 문자 하나 (줄바꿈 제외) | a.b | aab, acb, a1b 등 a와 b 사이 한 글자가 있는 문자열과 매치 |
[] | 괄호 안의 문자 중 하나 | [abc] | a, b, c 중 하나와 매치 |
[^] | 괄호 안 문자를 제외한 나머지 | [^0-9] | 숫자를 제외한 모든 문자와 매치 |
^ | 문자열의 시작 | ^A | 문자열이 A로 시작해야 함 |
$ | 문자열의 끝 | z$ | 문자열이 z로 끝나야 함 |
| | | OR (또는) | cat|dog | cat 또는 dog 문자열과 매치 |
^, $를 함께 사용하면 정확히 일치하는 문자열만 찾아낼 수 있다. 예를 들어, ^Hello$는 오직 "Hello"라는 문자열하고만 일치한다. 아이디나 비밀번호 규칙 검증 시 사용할 수 있다.
패턴이 몇 번이나 반복되는지를 지정할 수 있다. 수량자는 바로 앞의 문자나 그룹에 적용된다.
| 수량자 | 의미 | 예시 | 설명 |
|---|---|---|---|
* | 0번 이상 반복 | a* | (빈 문자열), a, aa, aaa... |
+ | 1번 이상 반복 | a+ | a, aa, aaa... |
? | 0번 또는 1번 (선택) | a? | (빈 문자열), a |
{n} | 정확히 n번 반복 | a{3} | aaa |
{n,} | n번 이상 반복 | a{2,} | aa, aaa... |
{n,m} | n번 이상 m번 이하 반복 | a{2,4} | aa, aaa, aaaa |
자주 사용하는 복잡한 패턴은 간단한 약어로 표현할 수 있다.
| 클래스 | 의미 | 동일 표현 |
|---|---|---|
\d | 숫자 (Digit) | [0-9] |
\D | 숫자가 아닌 것 | [^0-9] |
\w | 알파벳, 숫자, 밑줄(_) (Word) | [a-zA-Z0-9_] |
\W | \w가 아닌 것 | [^a-zA-Z0-9_] |
\s | 공백 문자 (Space) | [ \t\n\r] |
\S | 공백 문자가 아닌 것 | [^ \t\n\r] |
정규식의 일부를 괄호 ()로 묶어 그룹으로 만들 수 있다. 그룹은 알고리즘 문제 해결 시 정보를 추출하는 데 도움을 준다.
수량자를 하나의 문자가 아닌 여러 문자 묶음에 적용하고 싶을 때 사용한다.
(ab)+는 'ab'가 한 번 이상 반복되는 'ab', 'abab', 'ababab'...와 매치된다.그룹과 매치된 부분 문자열을 별도로 잡아내어(캡처하여) 나중에 사용할 수 잇다. Java에서는 matcher.group(n)과 같은 형태로 캡처된 문자열을 가져올 수 있다.
위의 문법을 활용해 코딩테스트 문제 유형에 적용해 보겠다.
문제 예시: "아이디는 5~20자의 소문자, 숫자, 빼기(-), 밑줄(_)만 사용가능하다."
^[a-z0-9_-]{5,20}$^: 문자열 시작[a-z0-9_-]: 허용되는 문자 집합 (소문자, 숫자, _, -){5,20}: 이 문자 집합이 5번 이상, 20번 이하 반복$: 문자열 끝문제 예시: "다트 게임 점수 10S*에서 점수(10), 보너스(S), 옵션(*)을 분리하시오."
(\\d{1,2})([SDT])([*#]?)(\\d{1,2}): 점수. 1~2자리 숫자를 캡처한다.([SDT]): 보너스. S, D, T중 하나를 캡처한다.([*#]?: 옵션. * 또는 #이 없거나 하나 있는 경우를 캡처한다.Pattern pattern = Pattern.compile("(\\d{1,2})([SDT])([*#]?)");
Matcher matcher = pattern.matcher("10S*");
if (matcher.find()) {
String score = matcher.group(1); // "10"
String bonus = matcher.group(2); // "S"
String option = matcher.group(3); // "*"
}
String[] result = str.split("[\\s,;]+");String result = str.replaceAll("\\s+", " ");
정규식 이제 무섭지 않다.