简体   繁体   中英

`break` and `continue` in `forEach` in Kotlin

Kotlin has very nice iterating functions, like forEach or repeat , but I am not able to make the break and continue operators work with them (both local and non-local):

repeat(5) {
    break
}

(1..5).forEach {
    continue@forEach
}

The goal is to mimic usual loops with the functional syntax as close as it might be. It was definitely possible in some older versions of Kotlin, but I struggle to reproduce the syntax.

The problem might be a bug with labels (M12), but I think that the first example should work anyway.

It seems to me that I've read somewhere about a special trick/annotation, but I could not find any reference on the subject. Might look like the following:

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

This will print 1 to 5. The return@forEach acts like the keyword continue in Java, which means in this case, it still executes every loop but skips to the next iteration if the value is greater than 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)
    }
}

This will print 1 to 10 but skips 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)
    }
}

Try them at Kotlin Playground .

Edit :
According to Kotlin's documentation , it is possible using annotations.

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

Original Answer :
Since you supply a (Int) -> Unit , you can't break from it, since the compiler do not know that it is used in a loop.

You have few options:

Use a regular for loop:

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

If the loop is the last code in the method
you can use return to get out of the method (or return value if it is not unit method).

Use a method
Create a custom repeat method method that returns Boolean for continuing.

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

A break can be achieved using:

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

And a continue can be achieved with:

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

As anyone here recommends... read the docs :P https://kotlinlang.org/docs/reference/returns.html#return-at-labels

EDIT: While the main question asks about forEach, it's important to consider the the good old "for". Using Kotlin doesn't mean we need to use forEach all the time. Using the good old "for" is perfectly ok, and sometimes even more expressive and concise than forEach:

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

You can use return from lambda expression which mimics a continue or break depending on your usage.

This is covered in the related question: How do I do a "break" or "continue" when in a functional loop within Kotlin?

As the Kotlin documentation says , using return is the way to go. Good thing about kotlin is that if you have nested functions, you can use labels to explicity write where your return is from:

Function Scope Return

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

and Local Return (it doesn't stop going through 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")
}

Checkout the documentation, it's really good :)

continue type behaviour in forEach

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

    // your code
}

for break type behaviour you have to use for in until or for in as per the list is Nullable or Non-Nullable

  1. For Nullable list:

     for (index in 0 until list.size) { val item = list[index] // you can use data item now if () { // your code break } // your code }
  2. For Non-Nullable list:

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

I have the perfect solution for this (:

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

Break statement for nested loops forEach():

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

Result:

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

Continue statement with anonymous function:

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

Result:

1 2 4 5 

maybe change forEach to

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

it works for hashmaps

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

       if(condition){
         doSomething()
       }else{
         break //or continue
        }
    }
  fun part2(ops: List<Int>): Int = ops.asSequence()
    .scan(0) { acc, v -> acc + v }
    .indexOf(-1)

If you can afford to turn a collection into a sequence , normally the cost is trivial, then you should be able to take advantage of the deferred feature.

You might already notice asSequence in the above. It's here for saving us going over the entire list. Right after we have a match via indexOf , it'll stop. Bingo! Saving us write a while here.

as in Part 2 of https://medium.com/@windmaomao/kotlin-day-1-up-and-down-38885a5fc2b1

If the condition depends on the outcome of a previous element in the list, you can use sequence and takeWhile to execute depth-first lazily.

sequenceOf(1, 2, 3, 4, 5).map { i ->
    println("i = ${i}")
    i < 3
}.takeWhile { success ->
    println("success = ${success}")
    success
}.toList()

will print

i = 1
success = true
i = 2
success = true
i = 3
success = false

You need the terminal toList() in the end to execute the sequence.

More details: https://kotlinlang.org/docs/sequences.html#sequence

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM