For-loop dynamic step


#1

Dear Kotlin community,

Is it possible to have a for-loop in Kotlin with a dynamic step (so it can change)?
I’m having trouble translating the following Java-code into Kotlin:

    for(int i = 0; i < 500; i+= (i < 40 ? 10 : 20)) {
           System.out.println("Now at "+ i);
       }

Is this possible?

I’ve read in an older post (How to for loop with var i) that there is no way to mutate i, so am I right to assume I’ll have to use while?

Thank you.


#2

I think, that you should have never used for loop for that in the first place and it is good that kotlin does not allow this usage. If I understand correctly, in your example variable i is not some kind of index, it has some meaning. And using such variables inside for loop could lead to some nasty side effects in a large code.

Variant using while is not much longer:

var i = 0
while( i < 500 ){
  println("Now at $i")
  i += if( i < 40 ) 10 else 20
}

#3

Hm, did not see the danger in that, but that’s probably due to my inexperience.
Thanks for the quick answer, much appreciated!


#4

The problem arises when you have a large code.

Pitfall number one: You accidentally or intentionally changing i somewhere inside your loop. You should obviously never do it, but changing the increment is the first step in this direction.

Pitfall number two: For some reason, you want to change the behavior of your dynamic step. You do it in one place, but forget to do in another and now you have two iterations with different number of steps.

Pitfall number three: Suppose you want a more complicated behavior and need three different steps instead of two. You won’t be able to do it with for anymore. You will have to either delegate step calculation to additional function which is even worse, or rewrite everything. In my example you can do it easily by replacing if by when.

Pitfall number four occurs when you have some parallel operations messing with i. In my example, you can simply replace i by AtomicInteger and make your operations thread safe.

There could be additional motivation, but in general I think that classic for loop should be deprecated and replaced by functional-style operations wherever it is possible.


#5

Try to switch your point of view, I hope this code is more readable.

fun main(args: Array<String>){
//sampleStart
    (0 until 40 step 10).asIterable()
            .plus(40 until 500 step 20)
            .forEach { i ->
                println("Now at " + i)
            }
//sampleEnd
}

Edit: use asSequence instead of asIterable, see below


#6

Hey that looks very intuitive and readable indeed!
Cheers!


#7

The asIterable is not necessary as it is already an Iterable. This can be shortened to the following (and yes the parentheses are necessary):

fun main(args: Array<String>) {
//sampleStart
    ((0 until 40 step 10) + (40 until 500 step 20))
            .forEach { println("Now at $it") }
//sampleEnd
}

#8

You are right,
however an Iterable plus another one produces an Iterable,
a Range plus another one produces a List.
Your code allocates the entire array an some -generally- hundreds of Integer (but it is more readable).


#9

@fvasco Iterable concatenated with another Iterable produces List, so again all its elements are materialized.
Did you mean asSequence()?


#10

Ops,
You are right, thanks for reply.


#11

I prefer this solution, I consider this enough readable and has minor memory impact than other implementations.

    import kotlin.coroutines.experimental.buildSequence

fun main(args : Array<String>){
//sampleStart
    buildSequence {
        yieldAll(0 until 40 step 10)
        yieldAll(40 until 500 step 20)
    }.forEach {
        println("Now at $it")
    }
//sampleEnd
}

This works if you are experimenting the “coroutinex” library. (False, see below)


#12

This works if you are experimenting the “coroutinex” library.

This works without kotlinx.coroutines


#13

This is more or less what I was about to go figure out how to respond, and you beat me to it. Well done!