Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple increments in Swift style for loops

Tags:

swift

swift3

I have the following loop in my Swift code:

for var i:Int = 0, j:Int = 0; i < rawDataOut.count; i += 4, ++j {
    maskPixels[j] = rawDataOut[i + 3]
}

I'm getting two warnings in Xcode:

  1. C-style for statement is deprecated and will be removed in a future version of Swift
  2. '++' is deprecated: it will be removed in Swift 3

I cannot see how to rewrite this in Swift's For In Loops to take into account the two variables and the 4-stride without it getting messy. Is there a simple elegant translation?

like image 827
dumbledad Avatar asked Mar 23 '16 16:03

dumbledad


1 Answers

Preconditions to answering this: looking over your example

I will assume that you intended to limit i by i < rawDataOut.count-3 rather than i < rawDataOut.count, otherwise the rawDataOut[i + 3] element access by index wouldn't make much sense w.r.t. runtime safety. Anyway, without you showing us rawDataOut, this is an assumption we shall have to make.

Hence, your original loop (including a verifiable example) looks as follows

/* example data */
var rawDataOut = Array(1...40)
var maskPixels = [Int](count: 10, repeatedValue: 0)

/* your pre-Swift 2.2/3.0 loop */
for var i: Int = 0, j: Int = 0; i < rawDataOut.count-3; i += 4, ++j {
    maskPixels[j] = rawDataOut[i + 3]
}

print(maskPixels) //[4, 8, 12, 16, 20, 24, 28, 32, 36, 40]

Using for in ... where

In your specific example case, the multiple increments have a simple relation (j=4*i) so we could solve this without even making use of an explicit j iterate, e.g.

/* example data */
var rawDataOut = Array(1...40)
var maskPixels = [Int](count: 10, repeatedValue: 0)

/* your pre-Swift 2.2/3.0 loop */
for i in 0..<(rawDataOut.count-3) where i%4 == 0 {
    maskPixels[i/4] = rawDataOut[i + 3]
}

print(maskPixels) // [4, 8, 12, 16, 20, 24, 28, 32, 36, 40]

but this is possibly not really of interest for the general discussion of two iterates. We move on to a 2-iterate method combining stride and enumerate.


Combining stride and enumerate

One solution is to enumerate the stride described by iterate i in your loop above, and use the enumeration index to construct the second iterate (j above). In your example, the enumerate index exactly corresponds to j, i.e.

/* example data */
var rawDataOut = Array(1...40)
var maskPixels = [Int](count: 10, repeatedValue: 0)

/* Swift >= 2.2 loop */
for (j, i) in 0.stride(to: rawDataOut.count-3, by: 4).enumerate() {
    maskPixels[j] = rawDataOut[i + 3]
}

print(maskPixels) // [4, 8, 12, 16, 20, 24, 28, 32, 36, 40]

Note that in Swift 3 (as compared to 2.2), it seems as if the default implementations of stride(to:by:) and stride(through:by:) to protocol Strideable (as non-blueprinted extension methods) will be depracated, and that we're back to (as in Swift 1.2) using the global functions stride(from:to:by:) and stride(from:through:by:), see e.g. the current master branch of swift/stdlib/public/core/Stride.swift


Trickier cases: resort to while

For trickier cases, say a signature in your pre-Swift 2.2 loop as

for var i: Int = 0, j: Int = 0; i < rawDataOut.count-3; i += 4, j += i { ... }

you're probably best of simply using a while loop with one iterate associated with the loop invariant, and a trailing iterate as a free variable; whether this is considered messy or not is a matter of taste, I suppose.

var i = 0, j = 0
while i < rawDataOut.count-3 {

    // ...

    // increase iterates
    i += 4
    j += i
}

I assume the reason for removing the C-style loop is that it's, in most cases, directly covered by for in, stride etc, and for cases where these don't suffice, a good old' while, most likely, will.

like image 136
dfrib Avatar answered Sep 29 '22 11:09

dfrib