Bresiu
Bresiu

Reputation: 2153

Replace all regex occurrences with item from array by index from regex group

I have text:

Lorem ipsum %NAME0% dolor sit amet %NAME1%, consectetur %NAME2% adipiscing elit.

and array names: [Bob, Alice, Tom]

I need to get X index from %NAMEX% and replace all %NAME0%, %NAME1%, %NAME2% with corresponding item from array: names.get(X).

I have

private fun String.replaceWithNames(names: List<String>): String {
    var result = this
    names.forEachIndexed { index, s ->
        result = result.replace("%NAME$index%", s)
    }
    return result
}

but I am sure that it can be done more optimally.

Upvotes: 1

Views: 200

Answers (2)

Wiktor Stribiżew
Wiktor Stribiżew

Reputation: 627335

You can use the following based on the kotlin.text.replace function:

val pattern = """%NAME(\d+)%""".toRegex()
fun String.replaceWithNames(names: List<String>): String {
    return this.replace(pattern, fun(m: MatchResult) : String { 
        return names.getOrNull(m.groupValues[1].toInt()) ?: m.value
    })
}

Or,

val pattern = """%NAME(\d+)%""".toRegex()
fun String.replaceWithNames(names: List<String>): String {
    return pattern.replace(this) { m ->
        names.getOrNull(m.groupValues[1].toInt()) ?: m.value
    }
}

See the online Kotlin demo:

import java.util.*
 
val pattern = """%NAME(\d+)%""".toRegex()
fun String.replaceWithNames(names: List<String>): String {
    return pattern.replace(this) { m ->
        names.getOrNull(m.groupValues[1].toInt()) ?: m.value
    }
}
 
fun main(args: Array<String>) {
    val lst = listOf("Bob", "Alice", "Tom")
    println( "Lorem ipsum %NAME0% dolor sit amet %NAME1%, consectetur %NAME2% adipiscing elit. Wrong %NAME11%".replaceWithNames(lst) )
}

Output:

Lorem ipsum Bob dolor sit amet Alice, consectetur Tom adipiscing elit. Wrong %NAME11%

The %NAME(\d+)% regex matches %NAME, then captures one or more digits into Group 1, and then matches a % char.

If names contains the index captured in Group 1, the replacement is the corresponding names item, else, it is the whole match.

Upvotes: 2

Joffrey
Joffrey

Reputation: 37799

You could use a regex to go through the string only once and replace each value using the last Regex.replace overload (with lambda). Also, I added some validation in case there is a name reference with an out of bounds index (your current code would just leave the reference there).

private val nameRefRegex = Regex("""%NAME(\d)%""")

private fun String.replaceWithNames(names: List<String>): String {
    return nameRefRegex.replace(this) { match ->
        val index = match.groupValues[1].toInt()
        require(index in names.indices) { "Invalid name index $index, only ${names.size} names available" }
        names[index]
    }
}

Note: if you need more than 10 names, you could change (\d) to (\d+) in the regex (to support more than 1 digit)

Upvotes: 4

Related Questions