알고리즘 문제를 풀면 풀수록 정규 표현식의 유능함이 느껴져서 이를 제대로 알아보고자 한다.
Java에선 정규 표현식을 쉽게 활용할 수 있도록 Java.util.regex API(Pattern, Matcher)를 제공한다.
활용 방법은 Oracle의 Java Tutorials와 Pattern API 문서에서 자세히 확인할 수 있으며
이번 글에선 Java Tutorials에서 설명하는 것을 따라 순서대로 정리해보고자 한다.
Java Tutorials - Lesson: Regular Expressions
https://docs.oracle.com/javase/tutorial/essential/regex/
Lesson: Regular Expressions (The Java™ Tutorials > Essential Java Classes)
The Java Tutorials have been written for JDK 8. Examples and practices described in this page don't take advantage of improvements introduced in later releases and might use technology no longer available. See Java Language Changes for a summary of updated
docs.oracle.com
Class Pattern API
https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html
Pattern (Java Platform SE 8 )
Enables canonical equivalence. When this flag is specified then two characters will be considered to match if, and only if, their full canonical decompositions match. The expression "a\u030A", for example, will match the string "\u00E5" when this flag is s
docs.oracle.com
정규 표현식
먼저 정규 표현식이 무엇인가에 대해 정리해 보자.
Wikipidia에선 정규 표현식(Regex: REGular EXpression)을 '특정한 규칙을 가진 문자열의 집합을 표현하는 데 사용하는 형식 언어'라고 정의하고 있다.
코드를 작성하다 보면 특정한 규칙에 따라 문자열의 일부를 추출 혹은 제거해야 하는 경우가 생기는데 정규 표현식을 활용하면 문제를 쉽게 해결할 수 있다.
정규 표현식은 Perl, Python. PHP. awk 등 다양한 종류가 있는데, Java는 Perl과 가장 유사하다.
Test Harness
Test Harness란 시험을 지원하는 목적으로 생성된 코드로,
Oracle에선 아래의 코드를 지원한다.
RegexTestHarness.java
import java.io.Console;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
public class RegexTestHarness {
public static void main(String[] args){
Console console = System.console();
if (console == null) {
System.err.println("No console.");
System.exit(1);
}
while (true) {
Pattern pattern =
Pattern.compile(console.readLine("%nEnter your regex: "));
Matcher matcher =
pattern.matcher(console.readLine("Enter input string to search: "));
boolean found = false;
while (matcher.find()) {
console.format("I found the text" +
" \"%s\" starting at " +
"index %d and ending at index %d.%n",
matcher.group(),
matcher.start(),
matcher.end());
found = true;
}
if(!found){
console.format("No match found.%n");
}
}
}
}
이 코드를 실행해 보면 콘솔에서 아래와 같은 문구가 나온다.
먼저 내가 원하는 regex를 작성하고 문자열을 입력하면 된다.
위 코드는 Pattern과 Matcher에 대해 알아본 후에 파헤쳐보고 일단 활용만 해보자.
확인하기 쉽도록
"I found the text" + " \"%s\" starting at " + "index %d and ending at index %d.%n" 를
"I found the text \"%s\" (%d, %d)%n" 로 수정한 후 활용할 계획이다.
아래는 수정한 코드이다.
import java.io.Console;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
public class RegexTestHarness {
public static void main(String[] args){
Console console = System.console();
if (console == null) {
System.err.println("No console.");
System.exit(1);
}
while (true) {
Pattern pattern =
Pattern.compile(console.readLine("%nEnter your regex: "));
Matcher matcher =
pattern.matcher(console.readLine("Enter input string to search: "));
boolean found = false;
while (matcher.find()) {
console.format("I found the text \"%s\" (%d, %d)%n",
matcher.group(),
matcher.start(),
matcher.end());
found = true;
}
if(!found){
console.format("No match found.%n");
}
}
}
}
String Literals
Regex의 가장 기본적인 지원 기능은 일치하는 문자열을 찾는 것이다.
예를 들어 "hello world"에서 "wo"를 찾고 싶을 때 간단히 regex를 "wo"로 작성하면 된다.
wo를 찾고 위치와 함께 반환했다.
그림으로 보면 아래와 같다.
이때 출력은 (6, 8)로 나왔지만 8은 포함하지 않음을 유의해야 한다.
(열린 구간, 닫힌 구간을 활용할까 했지만 보기 안 좋아서 그냥 놔뒀다..)
만약 일치하는 문자열이 여러 개면 모두 찾는다.
"wow! hello world"에는 "wo"가 2개이기 때문에 2번 찾은 모습이다.
다음 예제는 좀 이상하다.
regex는 "cat."을 넣었는데 일치하지 않음에도 "cats"를 반환했다.
그 이유는 점(.)이 모든 문자를 의미하는 메타문자(Meta Character)이기 때문이다.
메타문자는 정규식을 작성하는 데 사용되는 특수 문자로( [ { \ ^ - = $ ! | ] } ) ? * + . 이 있다.
이 특수 문자들을 일반 문자처럼 활용하려면 \를 붙여야 한다.
"cat\."로 작성해 보자.
"cats"는 찾지 못하고 "cat."은 찾은 모습이다.
이 문자들은 이후 하나하나 등장한다.
Character Classes(문자 클래스)
문자 클래스는 [ ]로 묶인 것을 의미한다.
[ ]는 문자 집합을 만드는 기호로 내부의 문자들이 하나의 문자를 의미하게 된다.
예를 들어 [abc]는 a, b, c 중 하나이다.
바로 활용해 보자.
string으로 넣은 "abcd"에서 a, b, c를 각각 찾은 모습이다.
다음은 regex로 "[abc]at"를 넣어보았다.
해당 표현식은 "aat", "bat", "cat"를 찾는다는 의미이며 이에 해당하는 "cat"가 출력됐다.
[^]
^ 는 부정의 의미로 해당 문자를 제외한다는 뜻이다.
예를 들어 "[^abc]"는 a, b, c를 제외한 모든 문자를 의미한다.
따라서 d, 공백, 특수문자 등만 출력된 것을 볼 수 있다.
다음 예는 "[^abc]at"를 표현식으로 활용한 것이다.
이 표현식은 "'aat', 'bat', 'cat'을 제외한 문자로 시작하고 at로 끝나는 것"을 의미한다.
string으로 "at cat eat at hello"를 넣었는데 결과로 "eat"와 " at"가 나왔다.
처음의 at는 앞에 아무런 문자가 없기 때문에 출력되지 않은 것이고
뒤의 at는 앞에 공백이라는 문자가 있기 때문에 출력된 것이다.
[-]
-는 범위를 뜻한다. 예를 들어서 "[a-z]"는 소문자 a부터 z까지를 의미한다.
굳이 a, z일 필요는 없다. "[a-c]"까지 했더니 "t"는 제외한 모습이다.
일반적으로 많이 사용하는 표현식은 "[a-zA-Z]"이다. 이는 공백, 특수문자, 숫자를 제외하고 문자만 찾을 수 있다.
[&&]
&&를 이용해서 합집합, 교집합, 차집합 등 다양하게 정규식을 엮을 수 있다.
합집합
아래처럼 작성하면 합집합으로 만들 수 있다. d만 출력이 되지 않은 모습이다.
교집합
다음은 a ~ e이고 c ~ g인 것을 의미한다. 따라서 이에 포함되는 c, d, e만 출력되었다.
차집합
아래는 a ~ z까지 중 b ~ d를 제외한 것을 의미한다. 따라서 a, e만 출력되었다.
유의할 점은 아래처럼 괄호 밖에 기호를 두면 제대로 나오지 않는다는 것이다.
Predefined Character Classes(미리 정의된 문자 클래스)
미리 정의된 문자 클래스는 0~9, a~z 등 자주 사용되는 문자 범위를 미리 정의한 것이다
내용은 아래 표와 같다.
아래 사진은 Patteran API에 나와있는 설명이다.
간단하게 활용해 보았다.
먼저 \d는 0 ~ 9이므로 1, 2, 3이 출력되었고,
\h는 수평 공백 문자이므로 " "(스페이스바)와 " "(탭)이 출력되었다.
Quantifiers(수량사)
수량사는 "양을 나타내는 한정사 혹은 대명사"라고 정의되어 있다.
즉, Quantifiers는 반복 횟수와 관련이 있는 것들이다.
Pattarn API에선 아래와 같이 설명하고 있는데 이를 하나씩 알아보자.
먼저 메타문자의 의미부터 확인해 보자.
바로 예제로 확인해 보자.
"?"이다. a를 0번 혹은 1번 출력하는 것이므로 0번까지 포함해서 나온 모습이다.
"*"이다. 0번 이상이므로 "aa"로 묶여서 한 번에 출력되었다.
"+"이다. 1번 이상이므로 0번은 나오지 않았다.
{n}이다. 정확이 n번 나와야 한다.
{n,}이다. n번 이상이 한 번에 묶여서 나온다.
{n,m}이다. n부터 m 사이에 맞게 출력한다.
이를 활용하면 다음과 같이 할 수 있다.
아래는 a or b or c를 4로 묶어서 출력하라는 것이다.
사실 이 부분은 내용이 어렵지 않다. 그런데 아래 사진을 다시 보면 quantifiers가 세 개로 구분되어 있다.
그 이유는 무엇일까?
이들의 차이는 구하고자 하는 문자열의 길이에 있다.
아래 예를 보자.
위부터 Greedy, Reluctant, Possessive 순이다.
Greedy는 최대한 많이, Reluctant는 최대한 적게 담는다.
Possessive는 자신이 가능한 많이 소유를 한다.
위의 예에선 .*+가 string을 전부 소유해 버려서 foo가 남아 "No match found"가 출력되었다.
Boundary Matchers(경계 매쳐)
Boundary Matchers는 매치되는 부분의 경계를 지정할 수 있다.
사용 기호는 다음과 같다.
여기서 사용되는 ^는 [ ] 안의 ^와는 역할이 다르다.
바로 예제를 보자.
^, $
"^cat$"은 문자열이 cat으로 시작하고 cat으로 끝나야 한다는 것이다.
즉, 그냥 문자열이 cat이어야 한다.
\b, \B
만약 중간에 있는 cat을 찾고 싶으면
단어의 경계를 의미하는 \b를 활용하면 된다.
"\bcat\b"와 "cat"의 차이점은 공백의 유무이다.
아래의 차이로 바로 구분할 수 있을 것이다.
위의 예제를 보면 \B도 쉽게 이해할 수 있다. 공백이 아니면 되는 것이다.
\G
\G는 직전 매치가 끝난 지점에서 곧바로 나와야 한다.
\A, \z
\A는 입력의 시작부,\z는 입력의 마지막 부를 찾는다.
Capturing Groups
Capturing Groups는 단어를 묶어 그룹화한다.
예를 들어 (cat){3}은 catcatcat이다.
그룹화하지 않은 cat{3}는 cattt이다.
내용이 길어져서 Pattern과 Matcher의 사용법은 새로 작성하겠다.
Java - 정규 표현식(Pattern, Matcher)
이전 글에 이어서 이번엔 Pattern과 Matcher를 알아보고자 한다. 이전 글: https://dlise.tistory.com/77 Java - 정규 표현식(Regex) 알고리즘 문제를 풀면 풀수록 정규 표현식의 유능함이 느껴져서 이를 제대로
dlise.tistory.com
'Java > Java' 카테고리의 다른 글
Java - Gradle Project 생성 (0) | 2023.09.11 |
---|---|
Java - 정규 표현식(Pattern, Matcher) (0) | 2023.09.10 |
Java - Thread에서 while문 안의 if문이 동작하지 않는 이유 (0) | 2023.09.01 |
Java - sort() (Comparator & Comparable) (0) | 2023.08.31 |
Java - 중위 - 후위 표기법 변환 & 후위 표기법 계산 (0) | 2023.08.24 |