`break` e` continue` em `forEach` em Kotlin

120

Kotlin tem funções de iteração muito boas, como forEachou repeat, mas não consigo fazer os operadores breake continuetrabalharem com eles (locais e não locais):

repeat(5) {
    break
}

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

O objetivo é imitar os loops usuais com a sintaxe funcional o mais próximo possível. Isso era definitivamente possível em algumas versões mais antigas do Kotlin, mas tenho dificuldade em reproduzir a sintaxe.

O problema pode ser um bug com rótulos (M12), mas acho que o primeiro exemplo deve funcionar mesmo assim.

Parece-me que li algures sobre um truque / anotação especial, mas não consegui encontrar nenhuma referência sobre o assunto. Pode ser parecido com o seguinte:

public inline fun repeat(times: Int, @loop body: (Int) -> Unit) {
    for (index in 0..times - 1) {
        body(index)
    }
}
voddan
fonte
1
No Kotlin atual, você pode simular isso (enquanto espera pelos recursos continue@labele break@label), consulte a pergunta relacionada: stackoverflow.com/questions/34642868/…
Jayson Minard
1
Esta pergunta pode ser esclarecida se você está perguntando apenas sobre a existência de breake continuepara loops funcionais, ou se está procurando respostas alternativas que façam exatamente a mesma coisa. O primeiro parece ser o caso, porque você rejeitou o último.
Jayson Minard
parece que eles são adicionados em kotlin 1.3
Tigran Babajanyan
@TigranBabajanyan uau! Você tem um link?
voddan
@voddan, não, eu apenas tentei funcionar
Tigran Babajanyan

Respostas:

69

Edit :
De acordo com a documentação do Kotlin , é possível usar anotações.

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

Resposta Original :
Como você fornece um (Int) -> Unit, não pode interromper ele, pois o compilador não sabe que ele é usado em um loop.

Você tem poucas opções:

Use um loop for regular:

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

Se o loop for o último código do método que
você pode usar returnpara sair do método (ou return valuese não forunit método).

Use um método
Crie um método de método de repetição personalizado que retorna Booleanpara continuar.

public inline fun repeatUntil(times: Int, body: (Int) -> Boolean) {
    for (index in 0 until times) {
        if (!body(index)) break
    }
}
Yoav Sternberg
fonte
Na verdade, minha pergunta era sobre fazer a sintaxe específica funcionar, não sobre iterar. Você não se lembra que foi possível em algum marco Kotlin?
voddan
1
Não me lembro. Mas talvez seja porque eu não uso muito break & continue. Veja esta edição , diz "Estimativa - Sem estimativa".
Yoav Sternberg
1
breake continuesó funcionam em loops. forEach, repeate todos os outros métodos são apenas isso: métodos e não loops. Yoav apresentadas algumas alternativas, mas breake continuenão são apenas ment trabalho para métodos.
Kirill Rakhman
@YoavSternberg Brilliant! Essa paz de velhos médicos é o que eu procurava! Portanto, o recurso ainda não foi implementado, deixando para versões futuras. Se você quiser criar uma resposta separada, vou marcá-la
voddan
No Kotlin atual, você pode simular isso (enquanto espera pelos recursos continue@labele break@label), consulte a pergunta relacionada: stackoverflow.com/questions/34642868/…
Jayson Minard
104

Isso imprimirá de 1 a 5. O return@forEachatua como a palavra-chave continueem Java, o que significa que, neste caso, ele ainda executa todos os loops, mas pula para a próxima iteração se o valor for maior 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)
    }
}

Isso imprimirá de 1 a 10, mas pula 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)
    }
}

Experimente-os no Kotlin Playground .

s-hunter
fonte
Ótimo, mas isso ainda não resolve o problema de não ser capaz de encerrar o forEach prematuramente quando alguma condição for atendida. Ele ainda continua executando o loop.
The Fox de
1
@TheFox sim, ele executa todos os laços e qualquer coisa após o retorno é ignorada quando a condição é atendida. Cada operação em forEach é uma função lambda; atualmente, não há operação de interrupção exata para a operação forEach. O intervalo está disponível em loops for, consulte: kotlinlang.org/docs/reference/returns.html
s-hunter
Aqui está um snippet executável do Kotlin Playground com um continuee um breakexemplo: pl.kotl.in/_LAvET-wX
ashughes
34

Uma pausa pode ser alcançada 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")
}

E um continuar pode ser alcançado com:

//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 qualquer um aqui recomenda ... leia os documentos: P https://kotlinlang.org/docs/reference/returns.html#return-at-labels

Raymond Arteaga
fonte
Ótima solução. Funciona muito bem. Embora pareça que sem usar, também @loopdá o mesmo resultado desejado.
Paras Sidhu
Na verdade, você pode omitir a tag explícita "@loop" e usar a implícita "@run". O aspecto principal aqui é o retorno local ao chamador do lambda. Observe que você precisa envolver o loop dentro de algum escopo para que possa retornar localmente a ele mais tarde.
Raymond Arteaga
17

Como diz a documentação do Kotlin , usandoreturn é o caminho a seguir. O bom do kotlin é que, se você tiver funções aninhadas, poderá usar rótulos para escrever explicitamente de onde vem o seu retorno:

Retorno do Escopo da Função

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

e Retorno Local (não para de passar por 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")
}

Confira a documentação, é muito bom :)

cesards
fonte
3
Aviso: return @ lit não paraforEach
Jemshit Iskenderov
Está correto. É intencional. A primeira solução sim, mas se você tiver instruções dentro de um loop, poderá escolher para onde deseja retornar / saltar. No segundo caso, se usarmos apenas return, ele irá parar ;-)
cesards
Ligando para Return @ lit likes Continue
pqtuan86
10

continue tipo de comportamento em 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 o breaktipo de comportamento que você tem que usar for in untilou for inconforme a lista é NullableouNon-Nullable

  1. Para lista anulável :

    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 lista não anulável :

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

Instrução Break para loops aninhados forEach ():

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

Continue a declaração com função anônima:

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

Resultado:

1 2 4 5 
alexrnov
fonte
0

talvez mude para cada para

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
fonte