Kotlin 动态扩展循环上边框

Kotlin 动态扩展循环上边框,kotlin,Kotlin,我面临以下问题。下面是我的方法,它替换字符串中从abs value到Math.abs(如| 7 |->到Math.abs(7))的所有垂直线: 我需要执行它的次数与新的表达式字符串长度相同。但当我运行应用程序时,传递参数“| 7 |”的循环只执行了3次。问题出在哪里?我如何解决它?正如Michael Butscher所说,for()循环的循环条件在开始时计算一次,而不是在循环中每次都计算一次。因此,在您的示例中,字符串以3个字符开始,因此它是循环的 Kotlin讨论板对此进行了讨论 一个直接的解

我面临以下问题。下面是我的方法,它替换字符串中从abs value到Math.abs(如| 7 |->到Math.abs(7))的所有垂直线:


我需要执行它的次数与新的
表达式
字符串长度相同。但当我运行应用程序时,传递参数“| 7 |”的循环只执行了3次。问题出在哪里?我如何解决它?

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

Kotlin讨论板对此进行了讨论

一个直接的解决方法可能是使用
while()
循环,它每次都会评估条件

但是,这仍然是非常低效的。一般来说,在循环中操作字符串是一个坏主意:在这里,每次都会为子字符串创建两个新的字符串对象,一个StringBuilder收集所有内容,然后为结果创建另一个字符串。如果字符串变长,则可能会出现问题,和/或你每秒要做上千次

通常,字符串操作的答案是显式使用StringBuilder,并在将数据转换回字符串(如果有的话)之前尽可能长地保留数据。因此,您可以创建一个StringBuilder,将其初始化为字符串参数,然后循环(使用
while()
loop)。您可以使用
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
,但不是显式创建它,而是使用标准库的函数(它为您创建了一个函数),允许您在lambda中作为
This
访问它,然后将其转换为字符串。由于所有函数体都在lambda中,给函数一个表达式体(而不是
{
..
}
)稍微简洁一些

我翻转了条件,称它为
inAbsBracket
,因此它以false开头,当我们在括号内时设置为true;这似乎更容易理解

它在原始字符串中的每个字符上循环。(这里您必须调用它
this@replaceAbs
,因为
这个
引用了
buildString()
lambda()中的StringBuilder。)这样做时,您不需要知道索引,所以直接循环字符更简单

然后它附加
Math.abs(
来代替
|
,或者附加原始字符串中的字符;它需要执行后一种操作,因为它正在创建原始字符串的副本,而不是就地操作它。(因为
这个
是lambda中的StringBuilder,它可以直接调用
append()


这是你能得到的最有效的方法;它只对字符串进行一次扫描,并且只为结果创建一个StringBuilder和一个字符串。希望它也相对容易阅读-至少,一旦你习惯了Kotlin的一些很酷的功能

最初输入for循环时,“until”操作符创建一个range对象,该对象在“expression.length”更改后不会调整其结尾。尝试一下while循环。这是我在这里得到的最好答案,非常感谢@谢尔盖·哈洛夫斯基,我的荣幸! 几乎任何不平凡的代码都可以得到改进(毫无疑问,包括我上面写的代码!),这是一个很好的练习,可以让您了解如何使代码变得更简单、更短、更易于阅读、更高效、更健壮、更易用和/或更通用。 有时这些事情会发生冲突,所以你必须平衡它们;但当他们一起工作的时候,感觉很棒。 (当然,有些东西是品味的问题。) 如果我给了你一些想法,那就不客气了!
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)
        }
    }