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

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

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 .


Ó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


Ó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 :)


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
    }

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 

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
        }
    }
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.