`break` и` continue` в `forEach` в Котлине

123

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

repeat(5) {
    break
}

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

Цель состоит в том, чтобы имитировать обычные циклы с функциональным синтаксисом как можно ближе. Это определенно было возможно в некоторых старых версиях Kotlin, но мне сложно воспроизвести синтаксис.

Проблема может заключаться в ошибке с этикетками (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/...
Джейсон Minard
1
В этом вопросе можно уточнить, спрашиваете ли вы только о существовании функциональных циклов breakи continueдля них, или ищете альтернативные ответы, которые делают то же самое. Кажется, что первое имеет место, потому что вы отвергли второе.
Jayson Minard
кажется добавили, что в котлине 1.3
Тигран Бабаджанян
@TigranBabajanyan вау! У вас есть ссылка?
voddan
@voddan, нет, я только что попробовал, работает
Тигран Бабаджанян

Ответы:

69

Изменить :
согласно документации Kotlin , это возможно с использованием аннотаций.

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:

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
    }
}
Йоав Штернберг
источник
Собственно, мой вопрос касался работы конкретного синтаксиса, а не повторения. Разве вы не помните, что это было возможно на каком-то этапе в Котлине?
voddan
1
Не помню. Но, возможно, это потому, что я не часто использую break & continue. См. Этот выпуск , там написано «Оценка - Нет оценки».
Йоав Штернберг,
1
breakи continueработаем только петлями. forEach, repeatа все остальные методы - это просто методы, а не циклы. Йоав представлены некоторые варианты , но breakи continueпросто не Мент работу методов.
Кирилл Рахман
@YoavSternberg Великолепно! Этот мир старых документов - это то, что я искал! Так что функция еще не реализована, оставлена ​​для будущих версий. Если вы хотите создать отдельный ответ, я
отмечу
В текущем Котлин вы действительно можете имитировать это ( во время ожидания для continue@labelи break@labelфункций), см связанный с этим вопрос: stackoverflow.com/questions/34642868/...
Джейсон Minard
106

Это напечатает от 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)
    }
}

Попробуйте их на площадке Kotlin Playground .

s-охотник
источник
Отлично, но это все еще не решает проблему невозможности преждевременно завершить forEach при выполнении какого-либо условия. Он все еще продолжает выполнение цикла.
The Fox
1
@TheFox да, он выполняет каждый цикл, и все, что происходит после возврата, пропускается, когда условие выполнено. Каждая операция в forEach является лямбда-функцией, в настоящее время нет точной операции прерывания для операции forEach. Перерыв доступен в циклах for, см .: kotlinlang.org/docs/reference/returns.html
s-hunter
Вот исполняемый фрагмент Kotlin Playground с примерами a continueи break: pl.kotl.in/_LAvET-wX
ashughes
36

Разрыва можно добиться с помощью:

//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дает тот же желаемый результат.
Парас Сидху,
Фактически, вы можете опустить явный тег «@loop» и использовать неявный «@run». Ключевым аспектом здесь является локальный возврат к вызывающему лямбду. Обратите внимание, что вам нужно обернуть цикл внутри некоторой области, чтобы вы могли локально вернуться к нему позже.
Раймонд Артеага,
Это действительно отвечает на вопрос, однако я думаю, что это, вероятно, неправильный путь для функционального программирования. Что нам нужно, так это из Лодаша, transformгде вы можете сломаться при определенных условиях, например. reduceReturnIf(acc, value, returnIf: func)
windmaomao
28

Вы можете использовать выражение return из лямбда, которое имитирует continueили в breakзависимости от вашего использования.

Это рассматривается в соответствующем вопросе: как мне выполнить «прерывание» или «продолжение» в функциональном цикле внутри Kotlin?

Джейсон Минард
источник
16

Как говорится в документации Kotlin , использование return- это правильный выбор. В kotlin хорошо то, что если у вас есть вложенные функции, вы можете использовать метки, чтобы явно указать, откуда происходит ваш возврат:

Функция 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")
}

и местный возврат (не прекращается forEach = continue)

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

Ознакомьтесь с документацией, это действительно хорошо :)

cesards
источник
3
Предупреждение: return @ lit не останавливаетсяforEach
Джемшит Искендеров
Это верно. Это предназначено. Это первое решение, но если у вас есть инструкции внутри цикла, вы можете выбрать, куда вы хотите вернуться / перейти. Во втором случае, если мы просто используем return, он остановится ;-)
cesards
Звонок «Возврат», лайки «Продолжить»
pqtuan86
12

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. Для списка, допускающего значение NULL :

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

    for (item in list) { // data item will available right away
        if () {
            // your code
            break
        }
    
        // your code
    }
Сумит Джайн
источник
2

Оператор Break для вложенных циклов 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
        }
    }
nexDev
источник
0

Я знаю, что это может быть не совсем связано с break continue for forEach, но так как 1.3.70у нас есть, scanчтобы мы могли получить хотя бы промежуточные результаты.

    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)

Итак, теперь, если вы можете собрать все результаты (аналогичные map), которые могут вам не понадобиться, однако, если вы позволите ему закончить, вы можете пропустить разрыв и просто проверить окончательный массив.

IMHO, базовое функциональное программирование на самом деле не учитывает transformоперацию, которая должна выпрыгнуть посередине, не завершив ее. Чтобы действительно решить эту проблему, нам нужно создать функцию, которая учитывает условие возврата, например.

  reduceReturnIf(acc, value, returnIf: func)
windmaomao
источник