How do I do a “break” or “continue” when in a functional loop within Kotlin?

后端 未结 4 1913
长发绾君心
长发绾君心 2020-12-04 16:09

In Kotlin, I cannot do a break or continue within a function loop and my lambda -- like I can from a normal for loop. For example, th

相关标签:
4条回答
  • 2020-12-04 17:02

    takeWhile stdlib function may be used instead of break.

    For example,

    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
    
    0 讨论(0)
  • 2020-12-04 17:05

    There are other options other than what you are asking for that provide similar functionality. For example:

    You can avoid processing some values using filter: (like a continue)

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

    You can stop a functional loop by using takeWhile: (like a break)

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

    A more complex, although nonsensical example where you want to do some processing, skip some resulting values, and then stop at a set of different conditions, would be:

    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
           }
    

    A combination of these functions tends to eliminate the need for continue or break. And there are endless different options here and more than can be documented. To get an idea of what can be done, it is best if you learn all of the functions available in the Kotlin standard library for collections, lazy sequences, and iterable.

    Sometimes there are cases where you have mutating state that still needs to break or continue and is hard to do in a functional model. You can make it work using more complex functions like fold and reduce combined with the filter and takeWhile functions but sometimes that is harder to grok. Therefore if you really want that exact behavior you can use return from lambda expression which mimics a continue or break depending on your usage.

    Here is a an example mimicking continue:

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

    And you can go more complicated and use labels when you having nesting or confusing situations:

    (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
        }
    }
    

    If you want to do a break you need something outside the loop that you can return from, here we will use the run() function to help us:

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

    Instead of run() it could be let() or apply() or anything naturally you have surrounding the forEach that is a place you want to break from. But you will also skip the code within the same block following the forEach so be careful.

    These are inlined functions so really they do not really add overhead.

    Read the Kotlin reference docs for Returns and Jumps for all the special cases including for anonymous functions.


    Here is a unit test proving this all works:

    @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)
    }
    
    0 讨论(0)
  • 2020-12-04 17:11

    forEach with break can be specificly substituted with any function:

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

    Or possibly even shorter:

    (1..20).any { x ->
        results2.add(x)
        x == 4 // break on true
    }
    
    0 讨论(0)
  • 2020-12-04 17:12

    If there's a need to use continue or break, it is not ideal to use forEach compare to normal for-loop

    If you really like to chain your command, and perform like a for-loop, use the normal functional chain, instead of forLoop

    E.g. for

        for(i in 0 until 100 step 3) {
            if (i == 6) continue
            if (i == 60) break
            println(i)
        }
    

    Use map as for-loop, filterNot as continue, and asSequence() & first for break

        (0 until 100 step 3).asSequence()
                .filterNot { it == 6 }
                .map { println(it); it }
                .first { it == 60 }
    
    0 讨论(0)
提交回复
热议问题