4

I have a small debate at work: Is it a good practice to calculate the size of the Array in swift before running over it's items? What would be a better code practice:

Option A:

    func setAllToFalse() {
        for (var i = 0; i < mKeyboardTypesArray.count; i++ ) {
            self.mKeyboardTypesArray[i] = false
        }
    }

or Option B:

    func setAllToFalse() {
        let typesCount = mKeyboardTypesArray.count
        for (var i = 0; i < typesCount; i++ ) {
            self.mKeyboardTypesArray[i] = false
        }
    }

All, of course, if I don't alter the Array during the loops.

I did go over the documentation, which states this:

The loop is executed as follows:

When the loop is first entered, the initialization expression is evaluated once, to set up any constants or variables that are needed for the loop. The condition expression is evaluated. If it evaluates to false, the loop ends, and code execution continues after the for loop’s closing brace (}). If the expression evaluates to true, code execution continues by executing the statements inside the braces. After all statements are executed, the increment expression is evaluated. It might increase or decrease the value of a counter, or set one of the initialized variables to a new value based on the outcome of the statements. After the increment expression has been evaluated, execution returns to step 2, and the condition expression is evaluated again.

halfer
  • 19,824
  • 17
  • 99
  • 186
Emil Adz
  • 40,709
  • 36
  • 140
  • 187
  • I think you'd have to either study the generated assembly code or wait until Swift is open-source. The Swift compiler is supposed to be very very very smart, there's lots of optimizations. Also if you're sure that the array doesn't change, you can use: `for val in array { ... }` which will be the fastest way for sure. – Kametrixom Jul 05 '15 at 22:44
  • @Kametrixom, you are right. Yet you can't use the for.. in syntax in the example shown above as the val you get is actually a let variable, and it can't be changed, So you have to go to the longer syntax. – Emil Adz Jul 05 '15 at 22:48
  • You can use `for var val in array { ... }` ;). But i know what you mean, you can use `for i in 0.. – Kametrixom Jul 05 '15 at 22:50
  • No, you can't, from what I have checked right now. – Emil Adz Jul 05 '15 at 22:53
  • Oh so sorry, i really thought that would be possible – Kametrixom Jul 05 '15 at 22:55
  • 1
    But I totally forgot about the 0.. – Emil Adz Jul 05 '15 at 22:56
  • Oh no actually it does work for me in Swift 2, you must be using 1.2 then, makes sense – Kametrixom Jul 05 '15 at 22:56
  • Yes, can't allow my self to develop a product on a beta version. – Emil Adz Jul 05 '15 at 22:57
  • 1
    Yeah I really like that Swift has lots of different for loop syntax(es?) – Kametrixom Jul 05 '15 at 22:58
  • Yes, yet non of this really answers the question. – Emil Adz Jul 05 '15 at 23:00
  • (That's why I use comments) – Kametrixom Jul 05 '15 at 23:07
  • IMO, If you are going to run a traditional for-loop then option A is better because it is safe, more dynamic, and also less code/variables you need to worry about. If the array ever changes size you won't run out of bounds. However in the sample code you posted, you really should use fast-enumeration (for-in) since you don't care about the size of the array changing. – Epic Byte Jul 06 '15 at 16:47

3 Answers3

1

The idiomatic way to say this in Swift is:

func setAllToFalse() {
    mKeyboardTypesArray = mKeyboardTypesArray.map {_ in false}
}

That way, there is nothing to evaluate and nothing to count.

In fact, this would make a nice Array method:

extension Array {
    mutating func setAllTo(newValue:T) {
        self = self.map {_ in newValue}
    }
}

Now you can just say:

mKeyboardTypesArray.setAllTo(false)

Alternatively, you could do it this way (this involves taking the count, but only once):

mKeyboardTypesArray = Array(count:mKeyboardTypesArray.count, repeatedValue:false)
matt
  • 515,959
  • 87
  • 875
  • 1,141
  • Doesn't really answer the question, but I generaly tend to use functional programming more, it can make de look much nicer and removes a lot of potential bugs – Kametrixom Jul 05 '15 at 23:09
  • I don't wanna start an argument, but I think he meant **Option A** or **Option B** – Kametrixom Jul 05 '15 at 23:23
  • 2
    Those are C-style for loops. That is not good Swift style. That is my answer. If you have a different answer, give your answer. Let the crowd decide. Don't keep nagging me with comments, please. Thanks. – matt Jul 05 '15 at 23:32
0

The loop condition is evaluated each time through the loop. Consider this experiment:

extension Array {
    var myCount: Int {
        get {
            println("here")
            return self.count
        }
    }
}

let a = [1, 2, 3, 4, 5]

for var i = 0; i < a.myCount; i++ {
    println(a[i])
}

Output:

here
1
here
2
here
3
here
4
here
5
here

You could see a small speed improvement from Option B, but I expect that the count property on Array is not expensive if the array is unchanged. It is potentially a good code practice anyway because it communicates to the reader that you expect the array size to remain constant for duration of the loop.

It is possible that the compiler would optimize array.count by detecting that nothing in the loop modifies the array, but it wouldn't be able to do that for array.myCount because of the println side effect.

vacawama
  • 150,663
  • 30
  • 266
  • 294
  • I think the entire question revolves around whether the optimizer can optimize away the "array.count" call. Not about calling a method with special side effects. – StilesCrisis Jul 06 '15 at 00:43
0

I have found that it is not, which can cause a crash if you're iterating through an array and removing items (for example). In current Swift syntax, this for loop

        for i in 0..<m_pendingCommands.count
        {
            if m_pendingCommands[i].packetID < command.packetID
            {
                m_pendingCommands.remove(at: i)
            }
        }

crashed halfway through the array, with a bad index.

I switched this to a while loop:

    var i: Int = 0
    while i < m_pendingCommands.count
    {
        if m_pendingCommands[i].packetID < ID
        {
            m_pendingCommands.remove(at: i)
        }
        else
        {
            i += 1
        }
    }
Oscar
  • 2,039
  • 2
  • 29
  • 39