Non-linear loops

Yesterday, someone was asking me about how to do loops for non-linear sequences. Normally, you just do stuff like:

for i in 1...5 {print(i)} // 1, 2, 3, 4, 5

And stuff like:

for i in 1.0.stride(through: 2.01, by: 0.1) {
    print(i) // 1.0, 1.1, ... 1.9, 2.0
}

for floating point. The 2.01 ensures you get that last value. You had to add some kind of epsilon when working with C-style for loops too because reasons and floating point math.

Screen Shot 2016-02-26 at 10.50.11 AM

Ditto stride:

Screen Shot 2016-02-26 at 10.53.28 AM

Leaving floating point aside, the question of the day had to do with non-linear sequences, for example, how do you do a loop for 1^-2 through 12^-2? Using map applies a function across each value, offering a simple non-linear value sequence:

Screen Shot 2016-02-26 at 10.55.37 AM

Doing math inside the for-in statement itself can quickly become ugly. It’s much nicer to map a named function across an integer sequence than implement the transformation directly in the for loop declaration.

Screen Shot 2016-02-26 at 10.57.16 AM

Of course, once you separate out the function like this, you can throw in any function at all in place. Just swap out inverseSquare with whatever function you’d prefer.

If your functionality depends on the count of iterations, for example doing some kind of trig across every 15°, then you’d want to pull out the count calculation, so it could be used both in the loop and the applied function, like this:

Screen Shot 2016-02-26 at 10.59.59 AM

Update: Joe Groff writes, “1.0.stride(through: 2.01, by: 0.1) will accrue error as you repeatedly add 0.1000….02. You can avoid the ugly epsilon by iterating exact values and scaling down, say by 10.0.stride(through: 20.0, by: 1.0).lazy.map { $0 * 0.1 }, which will produce more accurate tenths.” If I were going to go that way, I think I’d prefer just using integer math and converting from there. Both solutions shown in the following screenshot.

Screen Shot 2016-02-26 at 11.26.58 AM

8 Comments

  • 1.0.stride(through: 2.01, by: 0.1) will accrue error as you repeatedly add 0.1000….02. You can avoid the ugly epsilon by iterating exact values and scaling down, say by 10.0.stride(through: 20.0, by: 1.0).lazy.map { $0 * 0.1 }, which will produce more accurate tenths.

    • If you want to be fussy about it, sticking to integer math and then converting to float would be nicer: for i in 10…20 { Double(i) / 10.0 }

  • Love the sine wave example á la “A picture is worth 10,000 words.”. Really solidifies the point (?). You should add a section on this topic to the next version of the Developer Cookbook!

  • This is great, thanks! Makes it much nicer for when you know the iterated sequence in advance.

    But with C-style for loops gone, how would you nicely handle a case when the contents of the for loop, through additional calculations decided the next index in an array, for example? Or when doing curve-smoothing, you’d add a variable number of new elements to the array, so your next index depends on the performed calculations.

    At the moment I have to resort to just a while loop with a variable declared above it, but C-style for loops worked really well for such uses. What do you recommend?

    • Show me an example for the kind of code you’re talking about…

  • congrats for making it onto Hacker News !

  • are the `by:` and comment/output not in sync for

    for i in 1.0.stride(through: 2.01, by: 0.1) {
    print(i) // 1.0, 1.2, … 1.8, 2.0
    }

    • Thanks for the catch! Fixed (I hope)