`перерва` та` продовження` в `forEach` в Котліні


120

Котлін має дуже хороші функції перебору, як forEachабо repeat, але я не в змозі зробити breakі continueоператори працюють з ними (як місцевий , так і нелокальним):

repeat(5) {
    break
}

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

Мета - імітувати звичайні петлі з функціональним синтаксисом як можна ближче. Це було напевно можливо в деяких старих версіях Котліна, але я намагаюся відтворити синтаксис.

Проблема може бути помилкою з мітками (M12), але я думаю, що перший приклад повинен працювати в будь-якому випадку.

Мені здається, я десь читав про спеціальний трюк / анотацію, але не зміг знайти жодної посилання на цю тему. Може виглядати наступним чином:

public inline fun repeat(times: Int, @loop body: (Int) -> Unit) {
    for (index in 0..times - 1) {
        body(index)
    }
}

1
У поточному Котліні ви дійсно можете імітувати це (очікуючи на характеристики continue@labelта break@labelфункції), дивіться відповідне запитання: stackoverflow.com/questions/34642868/…
Jayson Minard

1
Це питання може використовувати роз'яснення щодо того, запитуєте ви лише про існування breakта continueфункціональні петлі, чи шукаєте альтернативні відповіді, які роблять точно те саме. Здається, перший випадок, тому що ви відхилили другий.
Джейсон Мінард

здається, додають, що в котліні 1.3
Тигран Бабаджанян

@TigranBabajanyan вау! У вас є посилання?
воддан

@voddan, ні, я просто спробував це працює
Tigran Babajanyan

Відповіді:


69

Редагувати :
Згідно з документацією Котліна , можна використовувати примітки.

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

Оригінальний відповідь :
Оскільки ви постачаєте a (Int) -> Unit, ви не можете його перервати, оскільки компілятор не знає, що він використовується в циклі.

У вас є кілька варіантів:

Використовуйте звичайний цикл:

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

Якщо цикл є останнім кодом у методі, який
ви можете використовувати returnдля виходу з методу (або return valueякщо він не є unitметодом).

Використовуйте метод
Створення користувацького методу повторення, який повертається Booleanдля продовження.

public inline fun repeatUntil(times: Int, body: (Int) -> Boolean) {
    for (index in 0 until times) {
        if (!body(index)) break
    }
}

Власне, моє запитання стосувалося того, щоб конкретний синтаксис працював, а не про ітерацію. Хіба не пам’ятаєте, що це було можливо на якомусь етапі Котліна?
воддан

1
Я не пам'ятаю. Але, можливо, це тому, що я не використовую перерву і продовжую багато. Дивіться це питання , в ньому написано "Оцінка - немає оцінки".
Йоав Штернберг

1
breakі continueпрацювати тільки в петлях. forEach, repeatа всі інші методи - це лише такі: методи, а не петлі. Йоав представлені деякі варіанти , але breakі continueпросто не Мент роботу методів.
Кирило Рахман

@YoavSternberg Блискуче! Цей спокій старих документів - це те, що я шукав! Тож функція ще не реалізована, залишена для майбутніх версій. Якщо ви хочете створити окрему відповідь, я
відзначу

У поточному Котліні ви дійсно можете імітувати це (очікуючи на характеристики continue@labelта break@labelфункції), дивіться відповідне запитання: stackoverflow.com/questions/34642868/…
Jayson Minard

104

Це надрукує від 1 до 5. return@forEachДії, як ключове слово continueв Java, що означає, що в цьому випадку він все ще виконує кожну цикл, але переходить до наступної ітерації, якщо значення перевищує 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)
    }
}

Це надрукує від 1 до 10, але пропускає 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)
    }
}

Спробуйте їх на дитячому майданчику Котлін .


Чудово, але це все ще не вирішує проблему неможливості припинити forEach передчасно, коли буде виконано якусь умову. Він все ще продовжує виконувати цикл.
Фокс

1
@TheFox так, він виконує кожен цикл і будь-що після повернення пропускається, коли виконується умова. Кожна операція forEach - це лямбда-функція, в даний час не існує точної операції розриву для операції forEach. Перерва доступна для циклів, дивіться: kotlinlang.org/docs/reference/returns.html
s-hunter

Ось фрагмент ігрового майданчика для котлінськогоcontinuebreak
ігрового

34

Перерву можна досягти, використовуючи:

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

А продовження можна досягти за допомогою:

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

Як хтось тут рекомендує ... читайте документи: P https://kotlinlang.org/docs/reference/returns.html#return-at-labels


Приємне рішення. Працює дуже добре. Хоча, схоже, і без використання @loopдає такий же бажаний результат.
Paras Sidhu

Насправді ви можете опустити явний тег "@loop" і використовувати неявний "@run". Ключовим аспектом тут є локальне повернення до абонента лямбда. Зверніть увагу, що вам потрібно зафіксувати цикл всередині деякого діапазону, щоб згодом ви могли повернути його локально.
Raymond Arteaga


17

Як говориться в документації Котліна , використання return- це шлях. Хороша річ у kotlin в тому, що якщо у вас є вкладені функції, ви можете використовувати мітки для чіткості написання, звідки ваше повернення:

Функція Сфера повернення

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

і Місцеве повернення (воно не припиняється через forEach = продовження)

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

Оформити документацію, це дійсно добре :)


3
Попередження: return @ lit не припиняєтьсяforEach
Джемсхіт Іскендеров

Це правильно. Це призначено. Перше рішення це робить, але якщо у вас є вказівки всередині циклу, ви можете вибрати, куди ви хочете повернутися / перейти. У другому випадку, якщо ми просто використаємо
функцію

Calling Return @ lit
like

10

continue тип поведінки в 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
}

для breakтипу поведінки, яку ви повинні використовувати for in untilабо for inвідповідно до списку, NullableабоNon-Nullable

  1. Для списку, що міняється :

    for (index in 0 until list.size) {
        val item = list[index] // you can use data item now
        if () {
            // your code
            break
        }
    
        // your code
    }
  2. Для списку, що не знімається :

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

2

Оператор розриву для вкладених циклів forEach ():

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

Результат:

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

Продовжити оператор з анонімною функцією:

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

Результат:

1 2 4 5 

0

можливо, змінити для кожного

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

він працює для хешмапів

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

       if(condition){
         doSomething()
       }else{
         break //or continue
        }
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.