코틀린 에서 정규 표현식 사용하기

GM.Lim
9 min readJan 29, 2019

--

Photo by Antoine Dautry on Unsplash

코틀린 에서 정규 표현식 을 사용하기 위해 제공하는 클래스 및 함수의 사용법을 설명합니다.

이 포스트 에서는 정규 표현식 의 구문 또는 작성법을 다루지 않습니다. 그러므로 문서의 이해를 위해서 최소한의 정규 표현식 지식이 필요합니다.

1. 정규 표현식 객체 만들기

코틀린은 Regex 클래스의 객체를 이용해서 정규 표현식 을 처리 합니다. 다음과 같은 3가지의 방법으로 만들수 있습니다.

  • Regex 클래스의 생성자를 이용
val reg1 = Regex("\\(([A-Z])\\w+\\)")
  • String 클래스의 확장함수 toRegex() 이용
val reg2 = "\\(([A-Z])\\w+\\)".toRegex()
  • Regex 클래스의 Companion Object 함수 인 fromLiteral() 이용
val reg3 = Regex.fromLiteral("\\(([A-Z])\\w+\\)")

입력값을 정규 표현식 으로 처리하는 첫번째, 두번째 방법과는 달리 세번째 방법인 fromLiteral() 은 함수의 이름에서 알수있듯이 입력값을 문자열 리터럴 로 처리 합니다.

즉, 입력값이 가진 모든 이스케이프 문자 를 모두 일반 문자 로 처리한다는 뜻이므로 reg3 은 오직 “\(([A-Z])\w+\)” 이라는 정확히 일치한 문자열 만 찾습니다.

예시에서 같은 입력값에 대해 reg3 은 reg1, reg2 와 는 다른 결과를 반환 한다는것에 주의해야 합니다.

2. 삼중 따옴표 를 이용한 정규 표현식 처리

(Apple) 이나 (Media) 처럼 괄호 안의 대문자로 시작하는 단어를 정규 표현식 으로 나타내면 다음과 같습니다.

\([A-Z]\w+\)

정규 표현식 을 사용하려면 다음과 같이 입력해야 합니다.

val regexString : String = "\\([A-Z]\\w+\\)"

\ (역슬래시) 는 코틀린에서 쓰이는 이스케이프 문자 이므로 직접 사용할수 없고 반드시 앞에 역슬래시를 추가 하여 해당 역슬래시는 일반 문자임을 표시해야 합니다.

복잡한 정규 표현식 을 작성하고 사용하다보면 많은 수의 역슬래시가 문자열에 추가되면서 본래의 식을 파악하기 어려워지는 경우를 흔히 볼수 있습니다.

이 경우, 삼중 따옴표를 이용해서 깔끔하게 표현할수 있습니다.

삼중 따옴표 안에서는 어떠한 이스케이프 문자도 따로 처리할 필요가 없으므로 다음과 같이 정규 표현식 그대로 작성할수 있습니다.

val regex = """\([A-Z]\w+\)""".toRegex()

3. 일치하는 요소 찾기

입력값이 정규 표현식 과 정확히 일치하는지 확인하고 싶다면 Regex 클래스의 matchEntire() 함수를 사용합니다.

val matchResult : MatchResult? = regex.matchEntire("abcd")

정규 표현식과 일치하는 첫번째 요소를 찾고 싶다면 find() 함수를 사용합니다.

val matchResult : MatchResult? = regex.find("abcd")

matchEntire() 와 find() 는 결과로 MatchResult 클래스의 객체를 반환합니다. 만약 결과값이 없을때는 null 이 반환 됩니다.

정규 표현식과 일치하는 모든 요소를 찾고 싶다면 findAll() 함수를 사용합니다.

val matchResult : Sequence<MatchResult> = regex.findAll("abcd")

findAll() 은 시퀀스 Sequence<MatchResult> 를 반환합니다. 결과값이 없을때 해당 시퀀스의 count 는 0 입니다.

4. MatchResult 클래스

Regex 클래스 의 메서드들은 MatchResult 클래스 객체에 결과값을 담아서 반환합니다.

4–1. 결과 확인

MatchResult 클래스 에는 value 프로퍼티는 정규 표현식을 이용하여 찾은 결과를 문자열로 담고 있습니다.

val matchResult : MatchResult? = regex.find("abcd")val value : String = matchResult!!.value

MatchResult 클래스의 range 프로퍼티는 입력값에서 value 의 값이 위치하는 첫번째 인덱스 와 마지막 인덱스를 IntRange 객체로 담고 있습니다.

val matchResult : MatchResult? = regex.find("abcd")val range : IntRange = matchResult!!.range

결과값이 여러개 일때는 MatchResult 클래스의 next() 함수를 이용하여 다음 결과를 조회할수 있습니다.

next() 함수는 다음 결과가 존재한다면 MatchResult 객체를 반환하고, 더이상 결과가 존재하지 않는다면 null 을 반환합니다.

그러므로 next() 함수 호출뒤에는 결과가 null 인가를 판단하여 사용하도록 합니다.

var matchResult : MatchResult? = regex.find("abcd")val value1 : String? = matchResult?.valuematchResult = matchResult?.next()val value2 : String? = matchResult?.value

4–2. 정규 표현식 그룹

정규 표현식 a([bc]+)d? 는 첫번째 그룹 a[bc]+d? 와 괄호 안의 두번째 그룹 [bc]+ 로 나눌수 있습니다.

그리고 그 결과는 MatchResult 클래스의 groupValues 프로퍼티 에 List<String> 객체로 저장되어 반환됩니다.

val regex = """a([bc]+)d?""".toRegex()val matchResult = regex.find("abcd")val groupValues : List<String> = matchResult!!.groupValues

MatchResult 클래스의 groups 프로퍼티는 MatchGroupCollection 의 객체 이므로 get 함수를 통하여 MatchGroup 객체를 반환받을수 있고, 이 MatchGroup 은 value 와 range 의 두개의 프로퍼티를 가집니다.

그러므로 그룹의 각 결과의 인덱스는 다음과 같이 구할수 있습니다.

val groupValueIndex : IntRange =
matchResult!!.groups!!.get(0)!!.range

4–3. 구조 분해 할당 (Destructuring)

정규 표현식 그룹을 이용해서 다음과 같이 구조 분해 할당을 사용할수 있습니다.

val regex = """([\w\s]+) is (\d+) years old""".toRegex()
val matchResult = regex.find("Mickey Mouse is 95 years old")
val (name, age) = matchResult!!.destructured

MatchResult 클래스의 destructured 프로퍼티는 Destructured 클래스를 이용하여 groupValues 에 담긴 각 그룹의 값들을 지정된 변수에구조 분해 할당 합니다.

예시에서 name 에는 Mickey Mouse 가, age 에는 95 가 할당되게 됩니다.

5. 교체

일치하는 요소를 찾는것 뿐만 아니라 일치하는 문자열을 다른 문자열로 대체 하기 위해서도 정규 표현식을 사용합니다.

Regex 클래스의 replace() 함수는 일치하는 모든 항목을 대치 합니다. replaceFirst() 함수는 일치하는 첫번째 항목을 대치 합니다.

각 함수는 대치 한 결과를 String 객체로 반환합니다.

val regex = """abc""".toRegex()

val result : String = regex.replace("abcdefg abcdefg", "!!!")

val result2 : String = regex.replaceFirst("abcdefg abcdefg", "!!!")

replace() 함수의 두번째 파라미터로 람다 함수를 사용할수 있습니다. 람다 함수를 사용하여 단순히 어떤 문자열로 대치 하는 것이 아닌 좀더 복잡한 변환 과정을 추가할수 있습니다.

val regex = """abc""".toRegex()

val result : String = regex.replace("abcdefghijk abcdefghijk") {
it.value.toUpperCase() + "!"
}

6. 자르기

정규 표현식의 문자열 찾기 와 문자열 교체 만큼이나 많이 사용 되는 경우는 특정 문자열을 기준으로 잘라서 목록화 하는것 입니다.

Regex 클래스의 split() 함수를 사용합니다. split() 함수는 List<String> 객체로 결과 목록을 반환합니다.

아래 예시는 공백을 기준으로 “abc” 가 4개 담긴 사이즈 4 인 문자열 리스트를 반환합니다.

val regex = """\W+""".toRegex()

val result : List<String> = regex.split("abc abc abc abc")

split() 함수의 두번째 파라미터는 최대 몇 마디로 자를것 인지를 지정할수 있습니다.

아래 예시는 “abc” 와 “abc abc abc” 가 담긴 사이즈 2 인 문자열 리스트를 반환합니다.

val regex = """\W+""".toRegex()val result : List<String> = regex.split("abc abc abc abc", 2)

7. 자바 와의 호환

자바와의 호환을 위하여 Regex 클래스는 toPattern() 함수를 제공 합니다.

toPattern() 함수는 자바에서 정규 표현식을 처리하기 위해 사용하는 java.util.regex.Pattern 객체로 변환 합니다.

val regex = """\W+""".toRegex()val pattern : Pattern = regex.toPattern()

8. 참고

--

--