获取 Kotlin-way 字符串中包含的子字符串的索引



我想实现一个函数,该函数将返回指定字符串中子字符串的索引。现在我用Java风格做了:

public fun String?.indexesOf(substr: String, ignoreCase: Boolean = true): List<Int> {
var list = mutableListOf<Int>()
if (substr.isNullOrBlank()) return list
var count = 0;
this?.split(substr, ignoreCase = ignoreCase)?.forEach {
count += it.length
list.add(count)
count += substr.length
}
list.remove(list.get(list.size-1))
return list
}

但我不认为这是一个 kotlin 方式的解决方案。它最像典型的Java程序,但用kotlin编写。如何使用 kotlin 更优雅地实现这一点?

我要做的是:

fun ignoreCaseOpt(ignoreCase: Boolean) = 
if (ignoreCase) setOf(RegexOption.IGNORE_CASE) else emptySet()
fun String?.indexesOf(pat: String, ignoreCase: Boolean = true): List<Int> =
pat.toRegex(ignoreCaseOpt(ignoreCase))
.findAll(this?: "")
.map { it.range.first }
.toList()
// check:
println("xabcaBd".indexesOf("ab", true))
println("xabcaBd".indexesOf("ab", false))
println("xabcaBd".indexesOf("abx", true))
val s: String? = null
println(s.indexesOf("aaa"))
// output:
[1, 4]
[1]
[]
[]

你可以把它压缩成这样:

public fun String?.indexesOf(substr: String, ignoreCase: Boolean = true): List<Int> {
return this?.let { 
val regex = if (ignoreCase) Regex(substr, RegexOption.IGNORE_CASE) else Regex(substr)
regex.findAll(this).map { it.range.start }.toList()
} ?: emptyList()
}

这是否更有效是另一回事。你必须测试一下。


如果您希望"aaa".indexesOf("aa")返回[0, 1]而不仅仅是[0],您应该能够通过修改正则表达式以使用积极的前瞻来做到这一点,即:

val regex = if (ignoreCase) Regex("(?=$substr)", RegexOption.IGNORE_CASE) else Regex("(?=$substr)")

正确的方法是使用String.indexOf(),因为splitting会忽略一些子字符串的出现。

例如,输入"aaaa">

和substr"aaa"("aaaa".indexesOf("aaa")(的结果应该是[0, 1]但是您的解决方案(使用split(将导致[0]

public fun String?.indexesOf(substr: String, ignoreCase: Boolean = true): List<Int> {
val list = mutableListOf<Int>()
if (this == null || substr.isBlank()) return list
var i = -1
while(true) {
i = indexOf(substr, i + 1, ignoreCase)
when (i) {
-1 -> return list
else -> list.add(i)
}
}
}

下面是一个不具有任何可变状态的尾递归示例:

fun String?.indexesOf(substr: String, ignoreCase: Boolean = true): List<Int> {
tailrec fun String.collectIndexesOf(offset: Int = 0, indexes: List<Int> = emptyList()): List<Int> =
when (val index = indexOf(substr, offset, ignoreCase)) {
-1 -> indexes
else -> collectIndexesOf(index + substr.length, indexes + index)
}
return when (this) {
null -> emptyList()
else -> collectIndexesOf()
}
}
"abcABCbcaabcabcaaabc".indexesOf("ddd")
// []
"abcABCbcaabcabcaaabc".indexesOf("abc", ignoreCase = false)
// [0, 9, 12, 17]
"abcABCbcaabcabcaaabc".indexesOf("abc", ignoreCase = true)
// [0, 3, 9, 12, 17]
null.indexesOf("abc", ignoreCase = true)
// []

它将找到子字符串的第一个索引,并递归地继续缩短它以查找下一个匹配项。

使用indexOf函数试试这个

fun String?.indexesOf(substr: String, ignoreCase: Boolean = false): List<Int> {
return this?.let {
val indexes = mutableListOf<Int>()
var startIndex = 0
while(startIndex in 0 until length){
val index = this.indexOf(substr, startIndex, ignoreCase)
startIndex = if(index != -1){
indexes.add(index)
index + substr.length
} else{
index
}
}
return indexes
} ?: emptyList()
}

我真的很喜欢@leetwinski和@Michael的答案。

Kotlin 有很多可能性,这真是太棒了:)

基于上述的另一种可能的解决方案:

fun String.indexesOf(substr: String, ignoreCase: Boolean = true) : List<Int> =
(if (ignoreCase) Regex(substr, RegexOption.IGNORE_CASE) else Regex(substr))
.findAll(this).map { it.range.first }.toList()
@JvmName("indexesOfNullable")
fun String?.indexesOf(substr: String, ignoreCase: Boolean = true) = this?.indexesOf(substr, ignoreCase) ?: emptyList()

这应该是李特温斯基的评论,但SO不让我写评论。

这是一个很好的解决方案,但请注意,如果您的查询字符串包含任何在正则表达式中具有特殊含义的特殊字符,则可能会给您带来不正确的结果,甚至是 PatternSyntaxException 并使您的应用程序崩溃。

所以,如果你想寻找文字匹配,你必须使用转义

所以代码将

fun ignoreCaseOpt(ignoreCase: Boolean) =
if (ignoreCase) setOf(RegexOption.IGNORE_CASE) else emptySet()
fun String?.indexesOf(query: String, ignoreCase: Boolean = true): List<Int> =
Regex.escape(query)       // to disable any special meaning of query's characters
.toRegex(ignoreCaseOpt(ignoreCase))
.findAll(this?: "")
.map { it.range.first }
.toList()

最新更新