Kotlin 中 `forEach` 中的 `break` 和 `continue`

Posted

技术标签:

【中文标题】Kotlin 中 `forEach` 中的 `break` 和 `continue`【英文标题】:`break` and `continue` in `forEach` in Kotlin 【发布时间】:2015-12-09 01:07:18 【问题描述】:

Kotlin 有非常好的迭代函数,例如 forEachrepeat,但我无法使 breakcontinue 运算符与它们一起工作(本地和非本地):

repeat(5) 
    break


(1..5).forEach 
    continue@forEach

我们的目标是用尽可能接近的函数语法来模拟通常的循环。在某些旧版本的 Kotlin 中绝对可以,但我很难重现语法。

问题可能是标签 (M12) 的错误,但我认为第一个示例应该可以正常工作。

在我看来,我在某个地方读到了一个特殊的技巧/注释,但我找不到关于这个主题的任何参考资料。可能如下所示:

public inline fun repeat(times: Int, @loop body: (Int) -> Unit) 
    for (index in 0..times - 1) 
        body(index)
    

【问题讨论】:

在当前的 Kotlin 中,您确实可以模仿这一点(在等待 continue@labelbreak@label 功能时),请参阅相关问题:***.com/questions/34642868/… 这个问题可以用来澄清您是否只询问 breakcontinue 是否存在功能循环,或者您是否正在寻找做同样事情的替代答案。前者似乎是这样,因为你拒绝了后者。 似乎它们是在 kotlin 1.3 中添加的 @TigranBabajanyan 哇!有链接吗? @voddan,不,我刚刚试过了 【参考方案1】:

这将打印 1 到 5。return@forEach 的作用类似于 Java 中的关键字 continue,这意味着在这种情况下,它仍会执行每个循环,但如果值大于 5,则会跳到下一次迭代。

fun main(args: Array<String>) 
    val nums = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
    nums.forEach 
       if (it > 5) return@forEach
       println(it)
    

这将打印 1 到 10,但会跳过 5。

fun main(args: Array<String>) 
    val nums = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
    nums.forEach 
       if (it == 5) return@forEach
       println(it)
    

在Kotlin Playground 试用。

【讨论】:

很好,但这仍然不能解决在满足某些条件时无法过早结束 forEach 的问题。它仍然继续执行循环。 @TheFox 是的,它执行每个循环,并且在满足条件时跳过返回之后的任何内容。 forEach 中的每个操作都是一个 lambda 函数,目前 forEach 操作没有确切的 break 操作。 break 在 for 循环中可用,请参阅:kotlinlang.org/docs/reference/returns.html 这是一个可运行的 Kotlin Playground sn-p,带有 continuebreak 示例:pl.kotl.in/_LAvET-wX【参考方案2】:

编辑: 根据Kotlin的documentation,可以使用注解模拟continue

fun foo() 
    listOf(1, 2, 3, 4, 5).forEach lit@ 
        if (it == 3) return@lit // local return to the caller of the lambda, i.e. the forEach loop
        print(it)
    
    print(" done with explicit label")

如果要模拟break,只需添加run

fun foo() 
    run lit@ 
        listOf(1, 2, 3, 4, 5).forEach 
            if (it == 3) return@lit // local return to the caller of the lambda, i.e. the forEach loop
            print(it)
        
        print(" done with explicit label")
    


原答案: 由于您提供了(Int) -&gt; Unit,因此您无法从中中断,因为编译器不知道它在循环中使用。

你有几个选择:

使用常规 for 循环:

for (index in 0 until times) 
    // your code here

如果循环是方法中的最后一个代码 您可以使用return 退出该方法(如果不是unit 方法,则使用return value)。

使用方法 创建一个自定义的重复方法方法,该方法返回Boolean 以继续。

public inline fun repeatUntil(times: Int, body: (Int) -> Boolean) 
    for (index in 0 until times) 
        if (!body(index)) break
    

【讨论】:

实际上,我的问题是关于使特定语法工作,而不是迭代。你不记得在 Kotlin 的某个里程碑上它是可能的吗? 我不记得了。但也许是因为我不经常使用 break & continue 。请参阅this issue,上面写着“估计 - 无估计”。 breakcontinue 只能在循环中工作。 forEachrepeat 和所有其他方法就是这样:方法而不是循环。 Yoav 提出了一些替代方案,但 breakcontinue 只是不适用于方法。 @YoavSternberg 太棒了!我一直在寻找这种旧文档的平静!所以这个功能还没有实现,留给未来的版本。如果您想创建一个单独的答案,我会标记它 在当前的 Kotlin 中,您确实可以模仿这一点(在等待 continue@labelbreak@label 功能时),请参阅相关问题:***.com/questions/34642868/…【参考方案3】:

可以使用以下方法实现休息:

//Will produce "12 done with nested loop"
//Using "run" and a tag will prevent the loop from running again.
//Using return@forEach if I>=3 may look simpler, but it will keep running the loop and checking if i>=3 for values >=3 which is a waste of time.
fun foo() 
    run loop@
        listOf(1, 2, 3, 4, 5).forEach 
            if (it == 3) return@loop // non-local return from the lambda passed to run
            print(it)
        
    
    print(" done with nested loop")

并且可以通过以下方式继续:

//Will produce: "1245 done with implicit label"
fun foo() 
    listOf(1, 2, 3, 4, 5).forEach 
        if (it == 3) return@forEach // local return to the caller of the lambda, i.e. the forEach loop
        print(it)
    
    print(" done with implicit label")

正如这里的任何人所建议的...阅读文档:P https://kotlinlang.org/docs/reference/returns.html#return-at-labels

编辑: 虽然主要问题是关于 forEach,但重要的是要考虑旧的“for”。使用 Kotlin 并不意味着我们需要一直使用 forEach。使用古老的“for”是完全可以的,有时甚至比 forEach 更具表现力和简洁:

fun foo() 
    for(x in listOf(1, 2, 3, 4, 5)
        if (x == 3) break //or continue
        print(x)
    
    print("done with the good old for")

【讨论】:

不错的解决方案。效果很好。虽然似乎不使用@loop 也能得到相同的预期结果。 其实你可以省略显式标签“@loop”而使用隐式标签“@run”。这里的关键方面是本地返回到 lambda 的调用者。请注意,您需要将循环包装在某个范围内,以便稍后在本地返回。 这确实回答了这个问题,但是我认为这可能不是函数式编程的正确路径。我们需要的是,来自 Lodash,transform,您可以在特定条件下中断,例如。 reduceReturnIf(acc, value, returnIf: func)【参考方案4】:

您可以使用return from lambda expression,它模仿continuebreak,具体取决于您的使用情况。

这在相关问题中有所涉及:How do I do a "break" or "continue" when in a functional loop within Kotlin?

【讨论】:

【参考方案5】:

作为Kotlin documentation says,使用return 是要走的路。 kotlin 的好处是,如果你有嵌套函数,你可以使用标签来明确地写出你的返回值来自哪里:

函数范围返回

fun foo() 
  listOf(1, 2, 3, 4, 5).forEach 
    if (it == 3) return // non-local return directly to the caller of foo()
    print(it)
  
  println("this point is unreachable")

本地返回(不会停止通过 forEach = continuation)

fun foo() 
  listOf(1, 2, 3, 4, 5).forEach lit@
    if (it == 3) return@lit // local return to the caller of the lambda, i.e. the forEach loop
    print(it)
  
  print(" done with explicit label")

查看文档,真的很好:)

【讨论】:

警告:return@lit 不会停止 forEach 没错。这是有意的。第一个解决方案,但如果你在循环中有指令,你可以选择你想返回/跳转到哪里。在第二种情况下,如果我们只使用 return 它将停止 ;-) 调用 Return@lit 喜欢继续【参考方案6】:

continueforEach 中键入行为

list.forEach  item -> // here forEach give you data item and you can use it 
    if () 
        // your code
        return@forEach // Same as continue
    

    // your code

对于break 类型行为,您必须使用for in untilfor in,列表为NullableNon-Nullable

    对于Nullable列表:

    for (index in 0 until list.size) 
        val item = list[index] // you can use data item now
        if () 
            // your code
            break
        
    
        // your code
    
    

    对于 Non-Nullable 列表:

    for (item in list)  // data item will available right away
        if () 
            // your code
            break
        
    
        // your code
    
    

【讨论】:

【参考方案7】:

我对此有完美的解决方案(:

list.apply forEach item ->
    if (willContinue(item)) return@forEach
    if (willBreak(item)) return@apply

【讨论】:

【参考方案8】:

forEach() 嵌套循环的中断语句:

listOf("a", "b", "c").forEach find@ i ->
    listOf("b", "d").forEach  j ->
        if (i == j) return@find
        println("i = $i, j = $j")
    

结果:

i = a, j = b
i = a, j = d
i = c, j = b
i = c, j = d

使用匿名函数继续语句:

listOf(1, 2, 3, 4, 5).forEach(fun(value: Int) 
    if (value == 3) return
    print("$value ")
)

结果:

1 2 4 5 

【讨论】:

【参考方案9】:
  fun part2(ops: List<Int>): Int = ops.asSequence()
    .scan(0)  acc, v -> acc + v 
    .indexOf(-1)

如果您有能力将集合变成sequence,通常成本微不足道,那么您应该能够利用延迟功能。

您可能已经注意到上面的asSequence。它在这里是为了节省我们检查整个列表的时间。在我们通过indexOf 进行匹配后,它就会停止。答对了!保存我们在这里写一个while

如https://medium.com/@windmaomao/kotlin-day-1-up-and-down-38885a5fc2b1的第 2 部分中所述

【讨论】:

【参考方案10】:

也许将 forEach 更改为

for(it in myList)
   if(condition)
     doSomething()
   else
     break //or continue
    
 

它适用于哈希图

 for(it in myMap)
     val k = it.key
     val v = it.value

       if(condition)
         doSomething()
       else
         break //or continue
        
    

【讨论】:

以上是关于Kotlin 中 `forEach` 中的 `break` 和 `continue`的主要内容,如果未能解决你的问题,请参考以下文章

<br> 在下一个 foreach 项目中插入空格

Kotlin forEach中实现break

Kotlin forEach中实现break

Kotlin forEach中实现break

Kotlin 之 forEach 跳出循环

Kotlin 学习之被我一直用错的“return@forEachIndexed/return@forEach”