是否可以在 Kotlin 中编写一个 for 循环,我可以在迭代期间将索引更改为我想要的任何值?

时间:2021-04-11 18:10:43

标签: for-loop kotlin

是否可以在 Kotlin 中编写一个 for 循环,以便在迭代期间将索引更改为我想要的任何值?

我知道我可以写为一个这样的循环:

for (int i = 0; i < n; i++) {
    ...
    if (condition) i = <any valid index>
}

但是我不能做我想做的事。我想写一个像java一样的for循环

i

但能够将 i 的值更改为我想要的任何值。有很多时候需要。

我目前的用例是检查标签是否有效。我使用 for 循环遍历文本。当当前字符等于“<”时,我找到下一个“>”的索引并检查它们之间的文本是否是有效的标签名称。如果是这样,我将 foreach 的值设置为 ">" 的索引。我找不到在 Kotlin 中使用 for 循环的方法,只能使用 while 循环。如果使用while循环,需要在外面声明索引,在循环内部处理增量,有点烦人,容易出错。

3 个答案:

答案 0 :(得分:1)

如您所见,…in… for 循环的索引在 Kotlin 中是只读的;你不能在循环内改变它。

主要有两种方法。首先,在某些情况下,您可能能够在迭代本身中包含跳转——也许迭代某个过程的结果,而不是一个简单的范围。这样可以使循环更清晰、更简洁;但它只适用于某些特定情况。

否则,恐怕一般情况只是像您建议的那样使用 while 循环:

var i = 1
while (i <= 3) {
    println(i)
    ++i
}

i = 6
while (i >= 0) {
    println(i)
    i -= 2
}

正如你所说,这当然更尴尬:初始值、增量/减量和最终值现在在三个不同的地方,因此循环更难阅读。并且循环变量不再被限制在循环本身的范围内,这会污染封闭函数/块的命名空间并在以后有混淆的风险(特别是如果变量被重用,如上所述)。

但它更通用:循环变量每次通过循环都可以调整任意量,即使是以事先未知的方式。

不幸的是,没有简单的方法可以将其包装成通用的高阶函数(这就是 Kotlin 允许伪造许多其他语言结构的方式^H^H^H^H^H^Himplemented)。这是出于同样的原因:lambda 参数(无论是调用 it 还是显式命名)都是只读的。

并预测您的下一个问题:我认为没有关于为什么 for 索引(如函数参数)是只读的官方说法,但我怀疑这是因为让它们可变会导致混淆和其他错误。 (如果您的 Java 示例又长又复杂,则可能很容易错过循环内对 i 的更新,并且从循环的顶部假设它每次只增加 1。 或者可能有可能会意外更新循环计数器,从而为不同的变量输入错误。)

答案 1 :(得分:0)

就像函数参数一样,for循环的变量也是只读的。如果不想使用 while 循环,则需要创建本地 var 才能对其进行修改。

for (x in 0..n) {
    var i = x
    //...
}

或者,对于您提到的用例可能更清楚:

for (x in 0..n) {
    val i = if (condition) x + 1 else x
    //...
}

如果您想知道为什么存在此限制,那是因为它被认为是允许它的语言中错误的常见来源。

答案 2 :(得分:0)

For 循环可以与实现 Iterable/Iterator 接口的任何对象一起使用,因此您可以执行以下技巧:

  1. 使用重置方法定义自定义迭代器
  2. 使这个迭代器在 next() 调用时返回不是一个简单的 Int,而是一个包含指向迭代器本身的链接的对象
  3. 为了方便添加扩展方法以从 IntProgression 检索此迭代器
interface Resettable {
    fun resetTo(x: Int)
}

class ResettableIterator(p: IntProgression) : Iterator<ResettableInt>, Resettable {
    private val last: Int = p.last
    private val step: Int = p.step
    private var delegate: IntIterator = p.iterator()

    override fun hasNext(): Boolean = delegate.hasNext()
    override fun next() = ResettableInt(delegate.nextInt(), this)
    override fun resetTo(x: Int) {
        delegate = IntProgression.fromClosedRange(x, last, step).iterator()
    }
}

data class ResettableInt(val value: Int, private val iterator: ResettableIterator) : Resettable by iterator

fun IntProgression.resettable() = ResettableIterator(this)

用法:

for (i in (1..10 step 2).resettable()) {
    if (condition(i.value)) i.resetTo(<any valid index>)
}

但是,我不建议使用这种方法,因为它不常见,因此相当混乱。

在你的情况下,我不明白你为什么需要使用低级 for-each char 循环 - regex one-liner 更适合于此:"<(.+?)>".toRegex().findAll(text).map { it.groupValues[1] }.all { isValid(it) }