`break` et` continue` dans `forEach` à Kotlin

121

Kotlin a de très belles fonctions d'itération, comme forEachou repeat, mais je ne suis pas en mesure de faire fonctionner les opérateurs breaket continueavec eux (à la fois locaux et non locaux):

repeat(5) {
    break
}

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

Le but est d'imiter les boucles habituelles avec la syntaxe fonctionnelle aussi proche qu'elle pourrait l'être. C'était certainement possible dans certaines anciennes versions de Kotlin, mais j'ai du mal à reproduire la syntaxe.

Le problème pourrait être un bug avec les étiquettes (M12), mais je pense que le premier exemple devrait quand même fonctionner.

Il me semble que j'ai lu quelque part sur une astuce / annotation spéciale, mais je n'ai trouvé aucune référence sur le sujet. Cela pourrait ressembler à ce qui suit:

public inline fun repeat(times: Int, @loop body: (Int) -> Unit) {
    for (index in 0..times - 1) {
        body(index)
    }
}
voddan
la source
1
Dans Kotlin actuel, vous pouvez en effet imiter cela (en attendant les fonctionnalités continue@labelet break@label), voir la question associée: stackoverflow.com/questions/34642868/...
Jayson Minard
1
Cette question pourrait aider à clarifier si vous ne posez des questions que sur l'existence breaket continuepour les boucles fonctionnelles, ou si vous recherchez des réponses alternatives qui font exactement la même chose. Le premier semble être le cas, car vous avez rejeté le second.
Jayson Minard
il semble qu'ils soient ajoutés que dans kotlin 1.3
Tigran Babajanyan
@TigranBabajanyan wow! avez vous un lien?
voddan
@voddan, non, je viens d'essayer que ça marche
Tigran Babajanyan

Réponses:

69

Edit :
Selon la documentation de Kotlin , il est possible d'utiliser des 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")
}

Réponse originale :
Puisque vous fournissez a (Int) -> Unit, vous ne pouvez pas vous en séparer, car le compilateur ne sait pas qu'il est utilisé dans une boucle.

Vous avez quelques options:

Utilisez une boucle for régulière:

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

Si la boucle est le dernier code de la méthode,
vous pouvez utiliser returnpour sortir de la méthode (ou return valuesi ce n'est pas une unitméthode).

Utiliser une méthode
Créez une méthode de méthode de répétition personnalisée qui renvoie Booleanpour continuer.

public inline fun repeatUntil(times: Int, body: (Int) -> Boolean) {
    for (index in 0 until times) {
        if (!body(index)) break
    }
}
Yoav Sternberg
la source
En fait, ma question portait sur le fonctionnement de la syntaxe spécifique, pas sur l'itération. Vous ne vous souvenez pas que c'était possible à une étape importante de Kotlin?
voddan du
1
Je ne m'en souviens pas. Mais c'est peut-être parce que je n'utilise pas souvent la pause et continue. Voir ce numéro , il dit "Estimation - Aucune estimation".
Yoav Sternberg
1
breaket continuene fonctionnent que par boucles. forEach, repeatet toutes les autres méthodes ne sont que cela: des méthodes et non des boucles. Yoav a présenté quelques alternatives mais breaket ne continuesont tout simplement pas à travailler pour les méthodes.
Kirill Rakhman
@YoavSternberg Brillant! Cette paix de vieux docs est ce que je cherchais! La fonctionnalité n'est donc pas encore implémentée, laissée pour les versions futures. Si vous souhaitez créer une réponse séparée, je la marquerai
voddan
Dans Kotlin actuel, vous pouvez en effet imiter cela (en attendant les fonctionnalités continue@labelet break@label), voir la question associée: stackoverflow.com/questions/34642868/...
Jayson Minard
105

Cela affichera 1 à 5. Il return@forEachagit comme le mot-clé continueen Java, ce qui signifie que dans ce cas, il exécute toujours chaque boucle mais passe à l'itération suivante si la valeur est supérieure à 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)
    }
}

Ceci imprimera 1 à 10 mais sautera 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)
    }
}

Essayez-les au Kotlin Playground .

locotracteur
la source
Génial, mais cela ne résout toujours pas le problème de ne pas pouvoir mettre fin prématurément à forEach lorsqu'une condition est remplie. Il continue toujours à exécuter la boucle.
The Fox
1
@TheFox oui, il exécute chaque boucle et tout ce qui est après le retour est ignoré lorsque la condition est remplie. Chaque opération dans forEach est une fonction lambda, actuellement il n'y a pas d'opération d'interruption exacte pour l'opération forEach. La pause est disponible dans les boucles for, voir: kotlinlang.org/docs/reference/returns.html
s-hunter
Voici un extrait de Kotlin Playground exécutable avec à la fois un continueet un breakexemple: pl.kotl.in/_LAvET-wX
ashughes
35

Une pause peut être obtenue en utilisant:

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

Et une poursuite peut être réalisée avec:

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

Comme tout le monde le recommande ici ... lisez la documentation: P https://kotlinlang.org/docs/reference/returns.html#return-at-labels

Raymond Arteaga
la source
Belle solution. Fonctionne très bien. Bien qu'il semble que le fait de ne pas utiliser @loopdonne également le même résultat souhaité.
Paras Sidhu le
En fait, vous pouvez omettre la balise explicite "@loop" et utiliser la balise implicite "@run". L'aspect clé ici est le retour local à l'appelant du lambda. Notez que vous devez envelopper la boucle dans une certaine portée afin que vous puissiez y revenir localement plus tard.
Raymond Arteaga le
Cela répond vraiment à la question, mais je pense que ce n'est probablement pas la bonne voie à suivre pour la programmation fonctionnelle. Ce dont nous avons besoin, c'est de Lodash, transformoù vous pouvez casser sous certaines conditions, ex. reduceReturnIf(acc, value, returnIf: func)
windmaomao il y a
16

Comme le dit la documentation Kotlin , utiliserreturn est la voie à suivre. La bonne chose à propos de kotlin est que si vous avez des fonctions imbriquées, vous pouvez utiliser des étiquettes pour écrire explicitement d'où vient votre retour:

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

et Local Return (il n'arrête pas de passer par 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")
}

Consultez la documentation, c'est vraiment bien :)

cesards
la source
3
Attention: return @ lit ne s'arrête pasforEach
Jemshit Iskenderov
C'est correct. C'est prévu. C'est la première solution, mais si vous avez des instructions à l'intérieur d'une boucle, vous pouvez choisir où voulez-vous retourner / sauter. Dans le second cas, si nous n'utilisons que return, cela s'arrêtera ;-)
cesards
Calling Return @ lit likes Continue
pqtuan86
10

continue comportement de type dans 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
}

pour le breakcomportement de type que vous devez utiliser for in untilou for inselon la liste est NullableouNon-Nullable

  1. Pour la liste Nullable :

    for (index in 0 until list.size) {
        val item = list[index] // you can use data item now
        if () {
            // your code
            break
        }
    
        // your code
    }
  2. Pour les listes non nulles :

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

Instruction Break pour les boucles imbriquées forEach ():

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

Résultat:

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

Continuez l'instruction avec la fonction anonyme:

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

Résultat:

1 2 4 5 
Alexrnov
la source
0

peut-être changer pour

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

ça marche pour les hashmaps

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

       if(condition){
         doSomething()
       }else{
         break //or continue
        }
    }
nexDev
la source
0

Je sais que cela n'est peut-être pas exactement lié à la pause forEach, mais depuis 1.3.70, nous l'avons fait scanpour que nous puissions au moins obtenir les résultats intermédiaires.

    fun charValue(c: Char) : Int {
      return if (c == '(') 1 else -1
    }

    val all = s.scan(0) { acc, c -> acc + charValue(c) }
    return all.indexOf(-1)

Alors maintenant, si vous pouvez assembler tous les résultats (similaires à map) dont vous pourriez ne pas avoir besoin, mais si vous l'autorisez à se terminer, vous pouvez ignorer la pause et simplement vérifier le tableau final.

À mon humble avis, la programmation fonctionnelle de base ne tient pas vraiment compte de l' transformopération qui doit sauter au milieu sans la terminer. Afin de vraiment résoudre ce problème, nous devons construire une fonction qui prend en compte la condition de retour, ex.

  reduceReturnIf(acc, value, returnIf: func)
Windmaomao
la source