오늘 배운 것

24-04-02 Kotlin에서 정규 표현식 사용하기 (2)

무무11 2024. 4. 2. 21:34

어제 정리하다 끝마치지 못했던 Kotlin에서 정규 표현식 사용하는 법에 대해 정리해보았다.

1. Kotlin에서 정규 표현식 사용하기

1-1. matchEntire(), matchAt(), find(), findAll()

위 4개의 함수는 정규식을 검사하여 매치되는 것이 있다면 MatchResult의 인스턴스를 반환하고 만약 매치되는 것이 없다면 null을 반환하는 함수들이다. 직접 예시를 들어 설명하는 것이 가장 이해가 빠르고 쉬웠기 때문에 다른 설명없이 함수를 하나하나 살펴보는 것이 좋을 것 같다.

 

- matchEntire()

 

matchEntire()는 입력값 전체에 대해 정규식을 검사하여 전체가 매치된다면 MatchResult의 인스턴스를 반환하고 매치되지 않으면 null을 반환한다.

fun main() {
    val text = "1.2.356"
    val reg1 = """\d[.]\d[.]\d""".toRegex()
    val reg2 = """\d[.]\d[.]\d+""".toRegex()

    println(reg1.matchEntire(text)) // null
    // 전체에 대해 매치되지 않으므로 null을 반환한다
    println(reg2.matchEntire(text)) // kotlin.text.MatcherMatchResult@300ffa5d
    // 전체에 대해 매치되므로 인스턴스가 반환된다
}

 

MatchResult의 value와 range 속성을 이용하면 매치 값과 해당 매치 값의 인덱스 범위를 얻을 수 있다.

fun main() {
    val text = "1.2.356"
    val reg1 = """\d[.]\d[.]\d""".toRegex()
    val reg2 = """\d[.]\d[.]\d+""".toRegex()

    println(reg1.matchEntire(text)) // null
    println(reg2.matchEntire(text)?.value) // 1.2.356
    /* matchEntire()는 null을 반환할 수도 있다. 따라서 safe call을 사용해야한다.
    전체 매치 값인 1.2.345이 출력된 것을 볼 수 있다.*/
    println(reg2.matchEntire(text)!!.value) // 1.2.356
    // null이 아님을 확신할 수 있다면 !!을 통해 non-nullable로 변환시키는 것도 가능하다.
    println(reg2.matchEntire(text)?.range) // 0..6
    // 전체 매치를 실행했으니 전체 인덱스 범위가 출력된다.
}

 

- matchAt()

 

matchAt()은 문자열과 인덱스를 인수로 받아, 지정된 인덱스에서 시작된 문자열이 정규식을 만족하면 MatchResult의 인스턴스를 반환하고, 그렇지 않으면 null을 반환한다. 해당 인덱스로부터 문자열 끝까지가 아니라 일부만 만족해도 MatchResult의 인스턴스가 반환된다. matchesAt()과 유사하므로 참고하면 이해가 쉬울 것이다.

fun main() {
    val text = "1.2.3456.7.8.90.1.2.3.456"
    val reg = """\d[.]\d[.]\d""".toRegex()

    println(reg.matchAt(text,0)) // kotlin.text.MatcherMatchResult@1f17ae12
    // 인덱스에서부터 정규식을 만족하는 문자열이 있으면 MatchResult의 인스턴스가 반환된다
    println(reg.matchAt(text,1)) // null
    // 인덱스에서부터 정규식을 만족하는 문자열이 없기 때문에 null이 반환된다
}

 

MatchResult의 value와 range 속성을 이용하면 매치 값과 해당 매치 값의 인덱스 범위를 얻을 수 있다.

fun main() {
    val text = "1.2.3456.7.8.90.1.2.3.456"
    val reg = """\d[.]\d[.]\d""".toRegex()

    println(reg.matchAt(text,0)?.value) // 1.2.3
    println(reg.matchAt(text,0)?.range) // 0..4
    println(reg.matchAt(text,7)?.value) // 6.7.8
    println(reg.matchAt(text,7)?.range) // 7..11
    println(reg.matchAt(text,9)?.value) // 7.8.9
    println(reg.matchAt(text,9)?.range) // 9..13
    println(reg.matchAt(text,18)?.value) // 2.3.4
    println(reg.matchAt(text,18)?.range) // 18..22
}

 

- find()

 

find()는 정규식을 만족하는 매치가 발견될 경우 해당하는 MatchResult의 인스턴스를 반환한다. 만족하는 매치가 문자열 전체에 없을 경우 null을 반환한다.

 

find()는 전체 문자열에 대해 정규식을 만족하는 첫 번째 매치를 기본적으로 반환한다. 인덱스를 추가 인자로 받을 수 있는데, 그 인덱스를 시작으로하는 문자열의 첫 번째 매치를 반환하게 된다. 인덱스를 넣지 않으면 기본값으로 0이 들어가 처음부터 검색하게 된다.

 

바로 위의 matchAt()의 경우는 지정된 인덱스로부터 매치가 시작되어야지만 인스턴스가 반환되었다. 하지만 find()는 지정된 인덱스부터 검색을 시작한다는 의미이기 때문에 꼭 지정된 인덱스부터 매치가 시작되지 않아도 이후 문자열에 매치가 있다면 인스턴스가 반환된다. 

fun main() {
    val text = "1.2.3456.7.8.90.1.2.3.456"
    val reg1 = """\d[.]\d[.]\d""".toRegex()
    val reg2 = """\d[.]\d[.]\W""".toRegex()

    println(reg1.find(text)) // kotlin.text.MatcherMatchResult@2e817b38
    println(reg1.find(text)?.value) // 1.2.3
    println(reg1.find(text)?.range) // 0..4
    // 가장 처음 매치되는 부분이 반환된다
    
    println(reg1.find(text,4)?.value) // 6.7.8
    println(reg1.find(text,4)?.range) // 7..11
    // 인덱스 4부터의 문자열에서 가장 처음 정규식을 만족하는 매치를 반환한다
    
    println(reg1.matchAt(text,4)?.value) // null
    // 인덱스 4에 해당하는 문자부터 매치가 되지 않기 때문에 null이 반환된다
    
    println(reg2.find(text)) // null
    // 전체에 만족하는 매치가 없어 null이 반환된다.
}

 

find()와 함께 next() 함수를 사용하면 첫 번째 매치 뿐 아니라 이후 매치들의 값도 얻어낼 수 있다.

fun main() {
    val text = "1.2.3456.7.8.90.1.2.3.456"
    val reg = """\d[.]\d[.]\d""".toRegex()

    val match = reg.find(text)

    println(match?.value) // 1.2.3
    println(match?.range) // 0..4

    val secondmatch = match?.next()

    println(secondmatch?.value) // 6.7.8
    println(secondmatch?.range) // 7..11

    val thirdmatch = secondmatch?.next()

    println(thirdmatch?.value) // 0.1.2
    println(thirdmatch?.range) // 14..18

    println(thirdmatch?.next()) // null
    // 다음 매치 값이 없기 때문에 null이 반환된다
}

 

- findAll()

 

findAll()은 전체 문자열에 대해 모든 매치를 찾아내어 MatchResult의 인스턴스들로 이루어진 시퀀스를 반환한다. 매치가 없다면 빈 시퀀스를 반환한다.

fun main() {
    val text = "1.2.3456.7.8.90.1.2.3.456"
    val reg1 = """\d[.]\d[.]\d""".toRegex()
    val reg2 = """\d[.]\d[.]\W""".toRegex()

    println(reg1.findAll(text).map {it.value}.joinToString())
    // 1.2.3, 6.7.8, 0.1.2
    /* 모든 매치에 대한 MatchResult들로 이루어진 시퀀스가 반환된다
    map 함수, joinToString() 같은 함수들로 값들을 출력해볼 수 있다 */
    println(reg2.findAll(text).none()) // true
    // 매치되는 곳이 없으므로 빈 시퀀스가 반환된다
}

 

 

오늘도 정리를 하다보니 시간이 생각보다 오래 걸려 끝까지 마무리하지 못했다. 그룹핑과 치환에 대해서 내일 정리하면 정규 표현식 사용법에 대해 모두 정리가 끝날 것 같다.

2. 오늘 알게된 것

- Kotlin에서 정규 표현식 사용하는 법에 대해 더 많이 알게 되었다.

- CS 공부를 열심히 하고 있는데 간단해 보였던 것 안에도 자세히 알아보면 수많은 내용들이 담겨있다는 걸 알 수 있었다. 더 열심히 공부해야겠다.