본문으로 건너뛰기

🔍 정규표현식

📖 정규표현식이란?

**정규표현식(Regular Expression, Regex)**은 문자열 패턴을 표현하는 방법입니다. 검색, 검증, 추출, 치환 등에 사용합니다!

💡 기본 사용법

Regex 생성

fun main() {
// Regex 객체 생성
val regex1 = Regex("hello")
val regex2 = "hello".toRegex()

// 매칭 확인
println(regex1.matches("hello")) // true
println(regex1.matches("Hello")) // false (대소문자 구분)
}

간단한 매칭

fun main() {
val text = "My email is hong@example.com"

// contains
val hasEmail = Regex("@").containsMatchIn(text)
println(hasEmail) // true

// find
val match = Regex("\\w+@\\w+\\.\\w+").find(text)
println(match?.value) // hong@example.com
}

🎯 기본 패턴

문자 클래스

fun main() {
// \d - 숫자
println("123".matches(Regex("\\d+"))) // true

// \w - 문자 (영문, 숫자, _)
println("hello_123".matches(Regex("\\w+"))) // true

// \s - 공백
println(" ".matches(Regex("\\s"))) // true

// . - 아무 문자
println("abc".matches(Regex("..."))) // true (3글자)
}

수량자

fun main() {
// + : 1개 이상
println("aaa".matches(Regex("a+"))) // true

// * : 0개 이상
println("".matches(Regex("a*"))) // true

// ? : 0개 또는 1개
println("a".matches(Regex("a?"))) // true

// {n} : 정확히 n개
println("aaa".matches(Regex("a{3}"))) // true

// {n,m} : n개 이상 m개 이하
println("aaa".matches(Regex("a{2,4}"))) // true
}

🎨 실전 예제

이메일 검증

fun isValidEmail(email: String): Boolean {
val pattern = "[a-zA-Z0-9._-]+@[a-z]+\\.+[a-z]+"
return email.matches(pattern.toRegex())
}

fun main() {
println(isValidEmail("hong@example.com")) // true
println(isValidEmail("invalid.email")) // false
println(isValidEmail("test@domain.co.kr")) // true
}

전화번호 검증

fun isValidPhone(phone: String): Boolean {
// 010-1234-5678 또는 01012345678
val pattern = "010-?\\d{4}-?\\d{4}"
return phone.matches(pattern.toRegex())
}

fun main() {
println(isValidPhone("010-1234-5678")) // true
println(isValidPhone("01012345678")) // true
println(isValidPhone("02-1234-5678")) // false
}

비밀번호 강도

fun checkPasswordStrength(password: String): String {
val hasLength = password.length >= 8
val hasDigit = Regex("\\d").containsMatchIn(password)
val hasLower = Regex("[a-z]").containsMatchIn(password)
val hasUpper = Regex("[A-Z]").containsMatchIn(password)
val hasSpecial = Regex("[!@#$%^&*]").containsMatchIn(password)

return when {
hasLength && hasDigit && hasLower && hasUpper && hasSpecial -> "매우 강함"
hasLength && hasDigit && (hasLower || hasUpper) -> "강함"
hasLength -> "보통"
else -> "약함"
}
}

fun main() {
println(checkPasswordStrength("Pass123!")) // 매우 강함
println(checkPasswordStrength("password123")) // 강함
println(checkPasswordStrength("password")) // 보통
println(checkPasswordStrength("pass")) // 약함
}

URL 추출

fun extractUrls(text: String): List<String> {
val pattern = "https?://[\\w./]+"
return Regex(pattern).findAll(text).map { it.value }.toList()
}

fun main() {
val text = """
Visit https://kotlin.org for info.
Check http://example.com too!
""".trimIndent()

val urls = extractUrls(text)
urls.forEach { println(it) }
// https://kotlin.org
// http://example.com
}

해시태그 추출

fun extractHashtags(text: String): List<String> {
val pattern = "#\\w+"
return Regex(pattern).findAll(text).map { it.value }.toList()
}

fun main() {
val tweet = "오늘 날씨 좋네요 #날씨 #행복 #코틀린"
val hashtags = extractHashtags(tweet)

println("해시태그:")
hashtags.forEach { println(it) }
// #날씨
// #행복
// #코틀린
}

🔧 고급 기능

그룹 캡처

fun parseDate(text: String): Triple<String, String, String>? {
val pattern = "(\\d{4})-(\\d{2})-(\\d{2})"
val match = Regex(pattern).find(text) ?: return null

val (year, month, day) = match.destructured
return Triple(year, month, day)
}

fun main() {
val date = parseDate("Today is 2024-12-25")
if (date != null) {
val (year, month, day) = date
println("Year: $year, Month: $month, Day: $day")
}
}

치환

fun main() {
val text = "My phone is 010-1234-5678"

// 전화번호 마스킹
val masked = text.replace(Regex("\\d{4}$"), "****")
println(masked) // My phone is 010-1234-****

// 여러 공백을 하나로
val multiSpace = "Hello World !"
val cleaned = multiSpace.replace(Regex("\\s+"), " ")
println(cleaned) // Hello World !
}

분리

fun main() {
// 쉼표나 공백으로 분리
val text = "apple, banana orange,grape"
val fruits = text.split(Regex("[,\\s]+"))
println(fruits) // [apple, banana, orange, grape]

// 숫자로 분리
val text2 = "item1,item2,item3"
val items = text2.split(Regex("\\d+"))
println(items) // [item, ,item, ,item, ]
}

🎯 실용 패턴

HTML 태그 제거

fun removeHtmlTags(html: String): String {
return html.replace(Regex("<[^>]*>"), "")
}

fun main() {
val html = "<p>Hello <b>World</b>!</p>"
val text = removeHtmlTags(html)
println(text) // Hello World!
}

숫자 추출

fun extractNumbers(text: String): List<Int> {
return Regex("\\d+").findAll(text)
.map { it.value.toInt() }
.toList()
}

fun main() {
val text = "I have 3 apples and 5 oranges"
val numbers = extractNumbers(text)
println(numbers) // [3, 5]
}

금액 포맷팅

fun formatCurrency(amount: Int): String {
return amount.toString().replace(Regex("(\\d)(?=(\\d{3})+$)"), "$1,")
}

fun main() {
println(formatCurrency(1000)) // 1,000
println(formatCurrency(1234567)) // 1,234,567
}

🤔 자주 묻는 질문

Q1. 대소문자 무시는?

A: RegexOption 사용!

fun main() {
val regex = Regex("hello", RegexOption.IGNORE_CASE)

println(regex.matches("Hello")) // true
println(regex.matches("HELLO")) // true
println(regex.matches("hello")) // true
}

Q2. 이스케이프는?

A: 백슬래시 두 개!

fun main() {
// . 을 문자 그대로 매칭
val dotRegex = Regex("\\.")
println("3.14".contains(dotRegex)) // true

// ( ) 을 문자 그대로
val parens = Regex("\\(.*\\)")
println("(test)".matches(parens)) // true
}

Q3. 성능은?

A: 컴파일된 Regex 재사용!

// ❌ 매번 컴파일
fun bad(text: String): Boolean {
return text.matches(Regex("\\d+")) // 느림
}

// ✅ 한 번만 컴파일
val NUMBER_REGEX = Regex("\\d+")
fun good(text: String): Boolean {
return text.matches(NUMBER_REGEX) // 빠름
}

📚 주요 패턴 정리

문자:
^ - 시작
$ - 끝
. - 아무 문자
\d - 숫자 [0-9]
\D - 숫자 아님
\w - 문자 [a-zA-Z0-9_]
\W - 문자 아님
\s - 공백
\S - 공백 아님

수량:
* - 0개 이상
+ - 1개 이상
? - 0개 또는 1개
{n} - 정확히 n개
{n,} - n개 이상
{n,m} - n개 이상 m개 이하

그룹:
(...) - 그룹
| - OR
[abc] - a, b, c 중 하나
[^abc] - a, b, c가 아닌 것
[a-z] - a부터 z까지

🎬 마치며

정규표현식으로 강력한 문자열 처리를!

핵심 정리:
✅ Regex()로 패턴 생성
✅ matches()로 전체 매칭
✅ find()로 부분 매칭
✅ replace()로 치환
✅ 패턴 재사용으로 성능 향상

다음 단계: DSL에서 코틀린다운 API를 만들어보세요!