`romper` y` continuar` en `forEach` en Kotlin

120

Kotlin tiene funciones de iteración muy agradables, como forEachor repeat, pero no puedo hacer que los operadores breaky continuefuncionen con ellos (tanto locales como no locales):

repeat(5) {
    break
}

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

El objetivo es imitar los bucles habituales con la sintaxis funcional lo más cerca posible. Definitivamente era posible en algunas versiones anteriores de Kotlin, pero me cuesta reproducir la sintaxis.

El problema puede ser un error con las etiquetas (M12), pero creo que el primer ejemplo debería funcionar de todos modos.

Me parece que he leído en algún lugar sobre un truco / anotación especial, pero no pude encontrar ninguna referencia sobre el tema. Podría tener el siguiente aspecto:

public inline fun repeat(times: Int, @loop body: (Int) -> Unit) {
    for (index in 0..times - 1) {
        body(index)
    }
}
voddan
fuente
1
En Kotlin actual, de hecho, puede imitar esto (mientras espera las funciones continue@labely break@label), consulte la pregunta relacionada: stackoverflow.com/questions/34642868/…
Jayson Minard
1
Esta pregunta podría necesitar una aclaración sobre si solo está preguntando sobre la existencia de breaky continuepara bucles funcionales, o si está buscando respuestas alternativas que hagan exactamente lo mismo. El primero parece ser el caso, porque rechazó el segundo.
Jayson Minard
parece que se agregan que en kotlin 1.3
Tigran Babajanyan
@TigranBabajanyan ¡guau! ¿Tienes un enlace?
voddan
@voddan, no, acabo de intentar que funcione
Tigran Babajanyan

Respuestas:

68

Editar :
De acuerdo con la documentación de Kotlin , es posible usar anotaciones.

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

Respuesta original :
Dado que proporciona a (Int) -> Unit, no puede separarse de él, ya que el compilador no sabe que se usa en un bucle.

Tienes pocas opciones:

Use un bucle for regular:

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

Si el bucle es el último código del método
que puede utilizar returnpara salir del método (o return valuesi no es un unitmétodo).

Utilice un método
Cree un método de repetición personalizado que regrese Booleanpara continuar.

public inline fun repeatUntil(times: Int, body: (Int) -> Boolean) {
    for (index in 0 until times) {
        if (!body(index)) break
    }
}
Yoav Sternberg
fuente
En realidad, mi pregunta era sobre cómo hacer que la sintaxis específica funcionara, no sobre iterar. ¿No recuerdas que fue posible en algún hito de Kotlin?
voddan
1
No recuerdo Pero tal vez sea porque no uso mucho break & continue. Vea este número , dice "Estimación - Sin estimación".
Yoav Sternberg
1
breaky continuesolo funciona en bucles. forEach, repeaty todos los demás métodos son solo eso: métodos y no bucles. Yoav presentó algunas alternativas, pero breaky continueno se Ment al trabajo de métodos.
Kirill Rakhman
@YoavSternberg ¡Brillante! ¡Esta paz de viejos doctores es lo que estaba buscando! Entonces, la función aún no está implementada, se deja para futuras versiones. Si desea crear una respuesta separada, la marcaré
voddan
En Kotlin actual, de hecho, puede imitar esto (mientras espera las funciones continue@labely break@label), consulte la pregunta relacionada: stackoverflow.com/questions/34642868/…
Jayson Minard
104

Esto imprimirá 1 a 5. El return@forEachactúa como la palabra clave continueen Java, lo que significa que en este caso, aún ejecuta cada ciclo pero salta a la siguiente iteración si el valor es mayor que 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)
    }
}

Esto imprimirá del 1 al 10 pero salta el 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)
    }
}

Pruébelos en Kotlin Playground .

guardagujas
fuente
Genial, pero esto aún no resuelve el problema de no poder finalizar forEach prematuramente cuando se cumple alguna condición. Todavía sigue ejecutando el ciclo.
The Fox
1
@TheFox sí, ejecuta cada ciclo y cualquier cosa después de la devolución se omite cuando se cumple la condición. Cada operación en forEach es una función lambda, actualmente no hay una operación de interrupción exacta para la operación forEach. La pausa está disponible en bucles for, consulte: kotlinlang.org/docs/reference/returns.html
s-hunter
Aquí hay un fragmento de Kotlin Playground ejecutable con un continuey un breakejemplo: pl.kotl.in/_LAvET-wX
ashughes
34

Se puede lograr un descanso usando:

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

Y se puede continuar con:

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

Como cualquiera aquí recomienda ... lea los documentos: P https://kotlinlang.org/docs/reference/returns.html#return-at-labels

Raymond Arteaga
fuente
Buena solucion. Funciona muy bien. Aunque parece que sin usar también @loopda el mismo resultado deseado.
Paras Sidhu
De hecho, puede omitir la etiqueta explícita "@loop" y utilizar la implícita "@run". El aspecto clave aquí es el retorno local al llamador de la lambda. Tenga en cuenta que debe envolver el bucle dentro de algún alcance para que pueda regresar localmente más adelante.
Raymond Arteaga
17

Como dice la documentación de Kotlin , usar returnes el camino a seguir. Lo bueno de kotlin es que si tiene funciones anidadas, puede usar etiquetas para escribir explícitamente de dónde proviene su devolución:

Retorno del alcance de la función

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

y Local Return (no deja de pasar forEach = continuación)

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

Mira la documentación, es realmente buena :)

cesar
fuente
3
Advertencia: return @ lit no se detieneforEach
Jemshit Iskenderov
Eso es correcto. Está previsto. La primera solución lo hace, pero si tiene instrucciones dentro de un bucle, puede elegir dónde desea regresar / saltar. En el segundo caso, si usamos return, se detendrá ;-)
cesards
Devolución de llamada @ lit me gusta Continuar
pqtuan86
10

continue tipo de comportamiento en 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
}

para el breaktipo de comportamiento que tiene que usar for in untilo for insegún la lista es NullableoNon-Nullable

  1. Para la lista que admite valores NULL :

    for (index in 0 until list.size) {
        val item = list[index] // you can use data item now
        if () {
            // your code
            break
        }
    
        // your code
    }
  2. Para la lista no anulable :

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

Sentencia de ruptura para bucles anidados para cada ():

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

Resultado:

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

Continuar declaración con función anónima:

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

Resultado:

1 2 4 5 
alexrnov
fuente
0

tal vez cambie para cada uno

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

funciona para hashmaps

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

       if(condition){
         doSomething()
       }else{
         break //or continue
        }
    }
nexDev
fuente