정규 표현(1)에서 기본적인 정규 표현 사용법과 코딩 스타일을 알아 보았습니다. 메타 기호 각각의 의미와 사용 방법에 대해 설명하는 것보다는 응용 예제를 다뤄보면서 메타 기호 사용 방법을 익혀나가는 게 도움이 될 겁니다.
■ 응용 1 : 날짜 및 휴대폰 번호 추출
먼저 문자열에서 날짜를 추출합니다. "\d"는 "[0-9]"와 같은 정규표현입니다. '\d'는 소문자 d가 아니라, 숫자([0-9])를 나타내는 메타 기호입니다. 소문자 d와 구분하기 위해 역슬래시(\)를 붙여 '\d'로 사용합니다. 문자열 따옴표(")안에서 사용할 때는 이스케이프(escape) 문자인 역슬래시(\)를 하나 더붙여 '\\d'로 사용해야 합니다. 이스케이프 문자를 추가하는 게 귀찮거나 헷갈리면(?) 삼중 따옴표(""")를 사용하면 됩니다. '\d{4}'는 숫자가 정확히 4개 있다는 뜻입니다. 즉, '\d{4}'는 길이가 4이며 숫자로만 이루어진 문자열 패턴입니다. matchResult는 정확히 부분 문자열 "2025-01-31"을 찾습니다.
fun main() {
val pattern = "\\d{4}-\\d{2}-\\d{2}".toRegex()
// val pattern = """\d{4}-\d{2}-\d{2}""".toRegex() // 3중 따옴표를 사용하면
// escape 문자(\)가 필요 없음.
val text = "오늘은 2025-01-31 입니다."
val matchResult: MatchResult? = pattern.find(text)
println("${matchResult?.range} ${matchResult?.value}")
// 4..13 2025-01-31
}
휴대폰 번호를 추출하려면 어떻게 하면 될까요? 아래처럼 가볍게(?) 수정하면 되겠죠!
val pattern = """\d{3}-\d{4}-\d{4}""".toRegex()
val text = "휴대폰 번호는 010-1234-5678 입니다."
■ 응용 2: 날짜에서 연/월/일 추출
앞의 예제에서 날짜 정보 중 연/월/일로 분리해 찾을 수 있을까요? 가능합니다. 우리가 찾는 패턴을 연/월/일 단위로 구분하면 됩니다. 아래처럼 연/월/일에 해당하는 패턴을 괄호로 묶으면 됩니다. matchResult 객체의 속성 groups를 사용해 연/월/일에 해당하는 문자열을 추출할 수 있습니다. matchResult?.groups?.forEachIndexed { ... } 문장은 그룹 단위로 추출할 때 흔히 사용하는 코딩 템플릿입니다. 추출 결과가 널일 수 있기 때문에 널 안전 연산자(?)를 붙여야 합니다.
여기서 주의할 점! 그룹 수는 3개가 아니라 4개입니다. 그룹 0은 전체 날짜 정보입니다.
fun main() {
val pattern = """(\d{4})-(\d{2})-(\d{2})""".toRegex()
val text = "오늘은 2025-01-31 입니다."
val matchResult = pattern.find(text)
matchResult?.groups?.forEachIndexed { index, group ->
println("Group $index: ${group?.value}")
}
// Group 0: 2025-01-31
// Group 1: 2025
// Group 2: 01
// Group 3: 31
}
위 코드가 설명을 위한 코드였다면, 아래 코드는 응용에 적용할 수 있는 실전(?) 코드입니다. 날짜 패턴을 찾았으면, 그룹 단위로 추출한 결과를 가져오기 위해 matchResult 객체의 속성 destructed를 사용해, 우리가 지정한 변수에 순서대로 할당합니다. 재미있는 점은 날짜 정보를 그룹 단위로 분할했기 때문에 그룹 수는 3개입니다.
fun main() {
val pattern = """(\d{4})-(\d{2})-(\d{2})""".toRegex()
val text = "오늘은 2025-01-31 입니다."
val matchResult = pattern.find(text)
if (matchResult != null) {
val (year, month, day) = matchResult.destructured
println("$year 년 $month 월 $day 일")
// 2025 년 01 월 31 일
}
}
조금 복잡하지만, 정규 표현에 그룹 이름을 지정할 수 있습니다. 범위 함수 let을 사용하며, let 블록 안으로 전달된 객체를 it로 참조할 수 있습니다. 여기서는 날짜 예제만 다뤘지만, 휴대폰 번호, 주민등록번호, 신용카드 번호처럼 숫자 패턴을 갖는 정보에 대해 쉽게 응용할 수 있습니다.
fun main() {
val pattern = """(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})""".toRegex()
val text = "오늘은 2025-01-31 입니다."
val matchResult = pattern.find(text)
matchResult?.groups?.let {
print("${it["year"]?.value} 년 ")
print("${it["month"]?.value} 월 ")
println("${it["day"]?.value} 일")
// 2025 년 01 월 31 일
}
}
■ 응용 3: 문자열을 부분문자열로 나누기(split)
split()은 아주 흥미로운 함수입니다. split()은 String 클래스에 속한 메소드이며, 구분 기호(delimiter)를 사용해 문자열을 여러 개의 부분문자열로 나눌 수 있습니다. 아래 예에서 구분 기호는 공백 문자(space, " ")입니다. 자연어 처리에서 필수적인 전처리 과정이 바로 문장을 단어 단위로 나누는 것입니다.
fun main() {
val text = "Hello Kotlin!"
println(text.split(" ")) // 구분자(delimiter)를 사용해 문자열 쪼개기
// [Hello, Kotlin!]
}
split() 함수의 파라미터로 정규 표현을 전달할 수 있습니다(Thanks to extension function!). '\s'는 공백 문자(space), 탭(tab), 또는 줄바꿈 문자(newline)를 가리킵니다. 이들 문자를 white space라고 부릅니다.
fun main() {
val pattern = "\\s".toRegex()
val text = "Hello Kotlin!"
println(text.split(pattern))
// [Hello, Kotlin!]
}
구분 기호를 2개 이상 지정하면 어떻게 될까요? 아래 예에서 구분 기호는 콤마(,)와 공백 문자(" ")이며, 결과는 같습니다.
fun main() {
val pattern = "[, ]".toRegex()
val text = "Hello, Kotlin!"
println(text.split(pattern)) // [Hello, , Kotlin!]
println(text.split(" ", ",")) // [Hello, , Kotlin!]
}
split() 함수의 파라미터로 구분 기호를 전달하거나 정규 표현을 전달하거나 큰 차이가 없는 듯 보이죠. 그렇다면, 아래 예도 결과가 같을까요? 중요한 차이점은 "\\s+"과 같이 메타 기호(+)를 추가한 점입니다. split() 함수의 파라미터로 공백 문자를 구분 기호로 전달하면, 여러 개 공백이 있으면 여러 개 부분 문자열로 나눕니다. 예를 들어 공백이 2개인 문자열은 2개의 부분문자열로 나뉩니다. split() 함수의 파라미터로 정규 표현("\\s+")을 전달하면, 공백 문자가 몇 개 있든 상관없이 모두 구분 기호로 취급할 수 있습니다. 즉, 정규 표현은 구분 기호의 패턴을 다양하게 정의할 수 있습니다.
fun main() {
val pattern = "\\s+".toRegex()
val text = "Hello Kotlin World"
println(text.split(pattern)) // [Hello, Kotlin, World]
println(text.split(" ")) // [Hello, , , Kotlin, , , World]
}
'코틀린' 카테고리의 다른 글
코틀린: 정규 표현(3) (0) | 2025.01.31 |
---|---|
코틀린: 정규 표현(1) (0) | 2025.01.27 |
쉬어가는 글: 람다 식은 어떻게 유래되었나요? (0) | 2025.01.27 |
코틀린: DSL(2/2) - CSV 빌더를 만들어 봅시다. (0) | 2025.01.17 |
코틀린: DSL(1/2) - CSV 빌더를 만들어 봅시다. (0) | 2025.01.17 |