web-dev-qa-db-fra.com

Comment faire une "pause" ou "continuer" dans une boucle fonctionnelle dans Kotlin?

Dans Kotlin, je ne peux pas faire de break ou continue dans une boucle de fonction et mon lambda - comme je peux le faire à partir d'une boucle normale de for. Par exemple, cela ne fonctionne pas:

(1..5).forEach {
    continue@forEach  // not allowed, nor break@forEach
}

Il y a ancienne documentation qui mentionne que cela est disponible mais il semble qu'il n'ait jamais été implémenté. Quelle est la meilleure façon d'obtenir le même comportement lorsque je veux continue ou break à partir du lambda?

Remarque: cette question est intentionnellement écrite et répondue par l'auteur ( questions en réponse ), de sorte que les réponses idiomatiques aux sujets Kotlin fréquemment posés sont présentes dans SO. Aussi pour clarifier certaines réponses vraiment anciennes écrites pour les alphas de Kotlin qui ne sont pas exactes pour le Kotlin actuel.

43
Jayson Minard

Il existe d'autres options que celles que vous demandez qui offrent des fonctionnalités similaires. Par exemple:

Vous pouvez éviter de traiter certaines valeurs en utilisant filter : ( comme un continue)

dataSet.filter { it % 2 == 0 }.forEach {
    // do work on even numbers
}

Vous pouvez arrêter une boucle fonctionnelle en utilisant takeWhile : ( comme un break)

dataSet.takeWhile { it < 10 }.forEach {
    // do work on numbers as long as they are < 10, otherwise stop
}

Un exemple plus complexe, bien que absurde, où vous souhaitez effectuer un traitement, ignorer certaines valeurs résultantes, puis vous arrêter à un ensemble de conditions différentes, serait:

dataSet.asSequence()
       .takeWhile { it >=  0 }    // a -1 signals end of the dataset (break)
       .map { it + 1 }            // increment each number
       .filterNot { it % 5 == 0 } // skip (continue) numbers divisible by 5
       .map { it - 1 }            // decrement each number by 1
       .filter { it < 100 }       // skip (continue) if number is >= 100
       .drop(5)                   // ignore the first 5 numbers
       .take(10)                  // use the next 10 numbers and end
       .forEach {
           // do work on the final list
       }

Une combinaison de ces fonctions a tendance à éliminer le besoin de continue ou break. Et il existe d'innombrables options différentes ici et plus que ce qui peut être documenté. Pour avoir une idée de ce qui peut être fait, il est préférable d'apprendre toutes les fonctions disponibles dans la bibliothèque standard de Kotlin pour collections , paresseux séquences et itérable .

Parfois, il existe des cas où vous avez un état de mutation qui doit encore break ou continue et est difficile à faire dans un modèle fonctionnel. Vous pouvez le faire fonctionner en utilisant des fonctions plus complexes comme fold et reduce combinées avec les fonctions filter et takeWhile mais parfois c'est plus difficile à comprendre. Par conséquent, si vous voulez vraiment ce comportement exact, vous pouvez utiliser retour de l'expression lambda qui imite un continue ou break selon votre utilisation.

Voici un exemple imitant continue:

(1..5).forEach  {
    if (it == 3) return@forEach  // mimic continue@forEach
    // ... do something more
}

Et vous pouvez aller plus compliqué et utiliser des étiquettes lorsque vous rencontrez des situations d'imbrication ou de confusion:

(1..3).forEach outer@ { x ->
    (1..3).forEach inner@ { y ->
        if (x == 2 && y == 2) return@outer // mimic continue@outer
        if (x == 1 && y == 1) return@inner // mimic continue@inner
        // ... do something more
    }
}

Si vous voulez faire un break vous avez besoin de quelque chose en dehors de la boucle que vous pouvez retourner, ici nous utiliserons la fonction run() pour nous aider:

run breaker@ {
    (1..20).forEach { x ->
        if (x == 5) return@breaker  // mimic break@forEach
        // ... do something more
    }
}

Au lieu de run(), il peut s'agir de let() ou apply() ou de tout ce que vous avez naturellement autour du forEach qui est un endroit dont vous voulez vous séparer. Mais vous sauterez également le code dans le même bloc après le forEach alors soyez prudent.

Ce sont des fonctions intégrées, donc elles n'ajoutent pas vraiment de surcharge.

Lisez les documents de référence de Kotlin pour Retours et sauts pour tous les cas spéciaux, y compris pour les fonctions anonymes.


Voici un test unitaire prouvant que tout fonctionne:

@Test fun testSo32540947() {
    val results = arrayListOf<Pair<Int,Int>>()
    (1..3).forEach outer@ { x ->
        (1..3).forEach inner@ { y ->
            if (x == 2 && y == 2) return@outer // continue @outer
            if (x == 1 && y == 1) return@inner // continue @inner
            results.add(Pair(x,y))
        }
    }

    assertEquals(listOf(Pair(1,2), Pair(1,3), Pair(2,1), Pair(3,1), Pair(3,2), Pair(3,3)), results)

    val results2 = arrayListOf<Int>()
    run breaker@ {
        (1..20).forEach { x ->
            if (x == 5) return@breaker
            results2.add(x)
        }
    }

    assertEquals(listOf(1,2,3,4), results2)
}
90
Jayson Minard

takeWhile La fonction stdlib peut être utilisée à la place de break.

Par exemple,

val array = arrayOf(2, 8, 4, 5, 13, 12, 16)
array.takeWhile { it % 2 == 0 }.forEach { println(it) } // break on odd
array.takeWhile { it % 3 != 0 }.forEach { println(it) } // break on 3 * n
1
user8320224

forEach avec break peut être spécifiquement remplacé par anyfunction :

(1..20).any { x ->
    (x == 5).apply { // break on true
        if (!this) {
            results2.add(x)
        }
    }
}

Ou peut-être encore plus court:

(1..20).any { x ->
    results2.add(x)
    x == 4 // break on true
}
0
Vadzim