`break` i` continue` w `forEach` w Kotlinie


122

Kotlin ma bardzo ładne funkcje iteracyjne, takie jak forEachlub repeat, ale nie jestem w stanie zmusić operatorów breaki do continuepracy z nimi (zarówno lokalnymi, jak i nielokalnymi):

repeat(5) {
    break
}

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

Celem jest naśladowanie zwykłych pętli przy możliwie najbliższej składni funkcjonalnej. Z pewnością było to możliwe w niektórych starszych wersjach Kotlina, ale mam problem z odtworzeniem składni.

Problemem może być błąd z etykietami (M12), ale myślę, że pierwszy przykład powinien i tak działać.

Wydaje mi się, że czytałem gdzieś o specjalnej sztuczce / adnotacji, ale nie mogłem znaleźć żadnego odniesienia na ten temat. Może wyglądać następująco:

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

1
W obecnym Kotlinie rzeczywiście możesz to naśladować (czekając na funkcje continue@labeli break@label), zobacz powiązane pytanie: stackoverflow.com/questions/34642868/…
Jayson Minard

1
To pytanie może wymagać wyjaśnienia, czy pytasz tylko o istnienie breaki continuedla pętli funkcjonalnych, czy też szukasz alternatywnych odpowiedzi, które robią dokładnie to samo. Wydaje się, że tak jest w pierwszym przypadku, ponieważ odrzuciłeś to drugie.
Jayson Minard

wydaje się, że są dodawane, że w kotlinie 1.3
Tigran Babajanyan

@TigranBabajanyan wow! Czy masz link?
voddan

@voddan, nie, właśnie próbowałem, to działa
Tigran Babajanyan

Odpowiedzi:


69

Edycja :
Zgodnie z dokumentacją Kotlina jest to możliwe przy użyciu adnotacji.

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

Oryginalna odpowiedź :
Ponieważ podajesz a (Int) -> Unit, nie możesz od niego zerwać, ponieważ kompilator nie wie, że jest używany w pętli.

Masz kilka opcji:

Użyj zwykłej pętli for:

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

Jeśli pętla jest ostatnim kodem w metodzie,
której możesz użyć returndo wyjścia z metody (lub return valuejeśli nie jest to unitmetoda).

Użyj metody
Utwórz niestandardową metodę powtarzania, która wraca Booleando kontynuacji.

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

Właściwie moje pytanie dotyczyło wykonania określonej składni, a nie iteracji. Nie pamiętasz, że było to możliwe na jakimś kamieniu milowym Kotlin?
voddan,

1
Nie pamiętam. Ale może to dlatego, że nie używam często przerwy i kontynuowania. Zobacz ten numer , mówi „Oszacowanie - Brak oszacowania”.
Yoav Sternberg

1
breaki continuedziałają tylko w pętlach. forEach, repeata wszystkie inne metody są po prostu: metodami, a nie pętlami. Yoav przedstawił kilka alternatyw, ale breaki continuepo prostu nie zamierza pracować nad metodami.
Kirill Rakhman

@YoavSternberg Brilliant! Ten spokój starych doktorów jest tym, czego szukałem! Więc funkcja nie została jeszcze zaimplementowana, pozostawiona dla przyszłych wersji. Jeśli zechcesz stworzyć osobną odpowiedź,
zaznaczę

W obecnym Kotlinie rzeczywiście możesz to naśladować (czekając na funkcje continue@labeli break@label), zobacz powiązane pytanie: stackoverflow.com/questions/34642868/…
Jayson Minard

106

Spowoduje to wyświetlenie od 1 do 5. return@forEachDziała jak słowo kluczowe continuew Javie, co oznacza, że ​​w tym przypadku nadal wykonuje każdą pętlę, ale przeskakuje do następnej iteracji, jeśli wartość jest większa niż 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)
    }
}

Spowoduje to wydrukowanie od 1 do 10, ale pominie 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)
    }
}

Wypróbuj je na placu zabaw Kotlin .


Świetnie, ale to nadal nie rozwiązuje problemu braku możliwości przedwczesnego zakończenia forEach, gdy spełniony jest jakiś warunek. Nadal wykonuje pętlę.
The Fox

1
@TheFox tak, wykonuje każdą pętlę i wszystko po powrocie jest pomijane, gdy warunek jest spełniony. Każda operacja w forEach jest funkcją lambda, obecnie nie ma dokładnej operacji przerwania dla operacji forEach. Przerwa jest dostępna w pętlach for, patrz: kotlinlang.org/docs/reference/returns.html
s-hunter

Oto gotowy docontinuebreak
uruchomienia

36

Przerwę można uzyskać za pomocą:

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

Kontynuację można osiągnąć dzięki:

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

Jak każdy tutaj zaleca ... przeczytaj dokumentację: P https://kotlinlang.org/docs/reference/returns.html#return-at-labels


Niezłe rozwiązanie. Działa bardzo dobrze. Chociaż wydaje się, że bez użycia również @loopdaje ten sam pożądany efekt.
Paras Sidhu

W rzeczywistości możesz pominąć jawny tag „@loop” i użyć niejawnego „@run”. Kluczowym aspektem jest tutaj lokalny powrót do obiektu wywołującego lambdę. Zauważ, że musisz zawinąć pętlę wewnątrz jakiegoś zakresu, aby móc później wrócić do niej lokalnie.
Raymond Arteaga

To naprawdę odpowiada na pytanie, jednak myślę, że prawdopodobnie nie jest to właściwa ścieżka do programowania funkcjonalnego. Potrzebujemy tylko firmy Lodash, transformgdzie można się zepsuć pod pewnymi warunkami, np. reduceReturnIf(acc, value, returnIf: func)
windmaomao


16

Jak mówi dokumentacja Kotlin , najlepszym rozwiązaniem returnjest używanie . Zaletą kotlin jest to, że jeśli masz zagnieżdżone funkcje, możesz użyć etykiet do bezpośredniego zapisu, skąd pochodzi zwrot:

Zwrot zakresu funkcji

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

i Local Return (nie przestaje przechodzić przez forEach = kontynuacja)

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

Sprawdź dokumentację, jest naprawdę dobra :)


3
Uwaga: powrót @ świeci się nie kończyforEach
Jemshit Iskenderov

To jest poprawne. To jest zamierzone. Jest to pierwsze rozwiązanie, ale jeśli masz instrukcje w pętli, możesz wybrać, dokąd chcesz wrócić / skoczyć. W drugim przypadku, jeśli użyjemy tylko powrotu, zatrzyma się ;-)
cesards

Calling Return @ lit Likes Kontynuuj
pqtuan86

10

continue zachowanie typu w 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
}

dla breakzachowania typu musisz użyć for in untillub for inzgodnie z listą jest NullablelubNon-Nullable

  1. W przypadku listy wartości zerowej :

    for (index in 0 until list.size) {
        val item = list[index] // you can use data item now
        if () {
            // your code
            break
        }
    
        // your code
    }
  2. Dla listy bez wartości null :

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

2

Instrukcja Break dla zagnieżdżonych pętli forEach ():

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

Wynik:

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

Kontynuuj instrukcję z funkcją anonimową:

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

Wynik:

1 2 4 5 

0

może zmienić forEach na

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

działa na hashmaps

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

       if(condition){
         doSomething()
       }else{
         break //or continue
        }
    }

0

Wiem, że to może nie do końca związane z przerwa trwać forEach, ale skoro 1.3.70mamy scantak, że możemy przynajmniej uzyskać wyniki pośrednie.

    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)

Więc teraz, jeśli możesz zebrać wszystkie wyniki (podobne do map), których możesz nie potrzebować, jednak jeśli pozwolisz na zakończenie, możesz pominąć przerwę i po prostu sprawdzić ostateczną tablicę.

IMHO, podstawowe programowanie funkcjonalne tak naprawdę nie uwzględnia transformoperacji, która musi wyskoczyć w środku, nie kończąc jej. Aby naprawdę rozwiązać ten problem, musimy skonstruować funkcję, która bierze pod uwagę warunek zwrotu, np.

  reduceReturnIf(acc, value, returnIf: func)
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.