【问题标题】:Get indexes of substrings contained in a string in Kotlin-way在 Kotlin-way 中获取字符串中包含的子字符串的索引
【发布时间】:2020-09-23 03:10:39
【问题描述】:

我想实现一个函数,它将返回指定字符串中子字符串的索引。现在我是用 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 更优雅地实现这一点?

【问题讨论】:

  • 至少你可以用list.dropLast(1)代替list.remove(list.get(list.size-1))
  • 只要记住可读性 > 简洁性

标签: kotlin


【解决方案1】:

我会做什么如下:

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]
[]
[]

【讨论】:

    【解决方案2】:

    你可以把它浓缩成这样的:

    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)")
    

    【讨论】:

    • @IR42 这意味着完全相同。 (它们都返回 EmptyList 单例对象。)
    • 如果子字符串包含括号,则返回 0 次。
    【解决方案3】:

    正确的方法是使用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)
            }
        }
    }
    

    【讨论】:

      【解决方案4】:

      这是一个不包含任何可变状态的尾递归示例:

      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)
      // []
      

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

      【讨论】:

      • 每次调用substring(index + substr.length) 都会产生新的字符串。这效率不高,尤其是在大字符串上
      • 我很想看看你的基准测试;)这个问题不是关于微优化的人。
      • @IR42 这是一个很好的改进。我会更新答案。
      【解决方案5】:

      我真的很喜欢@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()
      

      【讨论】:

      • PS:@JvmName("indexesOfNullable") 注解是因为 JVM 方法签名冲突。要查看完整消息,请删除 IntelliJ IDEA 中的注释。
      【解决方案6】:

      使用 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()
      }
      

      【讨论】:

        猜你喜欢
        • 2015-06-18
        • 2017-10-19
        • 2020-08-26
        • 2023-03-11
        • 1970-01-01
        • 2021-07-19
        • 2011-11-21
        • 1970-01-01
        • 2011-03-31
        相关资源
        最近更新 更多