动态扩展循环顶部边界



我遇到了以下问题。下面是我的方法,它将字符串中从abs值到Math.abs的所有垂直线(如|7|->到Math.aabs(7((替换为

private fun replaceAbs(_expression: String): String {
var expression = _expression
var isOpenAbsBracket = true
for (i in 0 until expression.length) {
if (expression[i] == '|') {
expression = if (isOpenAbsBracket) {
isOpenAbsBracket = false
"${expression.substring(
0,
i
)}Math.abs(${expression.substring(i + 1)}"
} else {
isOpenAbsBracket = true
"${expression.substring(
0,
i
)})${expression.substring(i + 1)}"
}
}
}
return expression
}

我需要执行它的次数与新的expression字符串长度一样多|7|";循环只执行了3次。问题出在哪里?我该怎么解决?

正如Michael Butscher所说,for ()循环的循环条件在开始时评估一次,而不是每次都通过循环。因此,在您的示例中,字符串以3个字符开始,因此它循环经过。

这在Kotlin讨论板上讨论过。

一个即时的解决方案可能是使用while ()循环,每次都会评估条件。

然而,这仍然是相当低效的。通常,在循环中操作字符串是个坏主意:在这里,每次都会为子字符串创建两个新的String对象,一个StringBuilder来收集所有内容,然后为结果创建另一个String。如果你的字符串变得很长,和/或你每秒要做数千个字符串,这可能会成为一个问题。

通常,字符串操作的答案是显式使用StringBuilder,并在将数据转换回字符串(如果有的话(之前尽可能长时间地保留数据。因此,您可以创建一个StringBuilder,将其初始化为字符串参数,然后在上面循环(使用while ()循环(。您可以使用replace()insert()进行更改。但这仍然是低效的,因为它每次都必须向上移动字符串的剩余部分。

因此,更好的解决方案是在原始字符串的字符上循环,将它们复制到StringBuilder中。我可以这样写:

private fun String.replaceAbs()
= buildString {
var inAbsBracket = false
for (c in this@replaceAbs) {
if (c == '|') {
inAbsBracket = !inAbsBracket
append(if (inAbsBracket) "Math.abs(" else ")")
} else
append(c)
}
}

我没有取一个参数,而是将其作为一个扩展函数(String.replaceAbs()(,所以您可以这样调用它:"|7|".replaceAbs()。(当然,它也可以作为一个正常的函数工作,但我发现这种扩展函数读起来很好。(

这使用了StringBuilder,但不是显式创建它,而是使用标准库的buildString()函数,该函数为您创建一个,允许您在lambda中以this的形式访问它,然后将其转换为String。因为所有的函数体都在lambda内部,所以给函数一个表达式体(=而不是{}(会稍微简洁一些。

我已经翻转了这个条件,称之为inAbsBracket,所以它从false开始,当我们在括号内时,它被设置为true;这似乎稍微容易一些。

它在原始字符串中的每个字符上循环。(这里必须将其称为this@replaceAbs,因为this指的是buildString()lambda中的StringBuilder。(这样做时不需要知道索引,因此直接在字符上循环更简单。

然后,它附加Math.abs()来代替|原始字符串中的字符;它需要执行后者,因为它正在创建原始字符串的副本,而不是在适当的位置操作它。(因为this是lambda内部的StringBuilder,所以它可以直接调用append()。(

这是你所能达到的效率;它对字符串进行一次扫描,并且只为结果创建一个StringBuilder和一个string。希望它也相对容易阅读——至少,一旦你习惯了Kotlin的一些酷功能!

最新更新