What the code should do
The array nums holds [1, 2, 3, 4, 5]. Every even number must go up by one and every odd number must go down by one, so the array ends up as [0, 3, 2, 5, 4]. The code you start with looks reasonable, but it doesn't compile:
var nums = [1, 2, 3, 4, 5]
for num in nums {
if num % 2 == 0 {
num += 1
} else {
num -= 1
}
}
The compiler stops with this error, once for num += 1 and once for num -= 1:
error: left side of mutating operator isn't mutable: 'num' is a 'let' constant
Why the loop variable can't change the array
In for num in nums, Swift creates the name num anew on every iteration and gives it the value of the current element. That name is a let constant, so it can't be assigned to. This is why the compiler complains.
Even if you could change it, the array would stay the same. Integers are value types, so num holds a copy of the element, not the element itself. The same thing happens here:
var number = 100
var numberCopy = number
numberCopy += 1
print(numberCopy) // 101
print(number) // 100
Changing a copy never touches the original. You can see it by shadowing the loop constant with a mutable variable of the same name:
var more = [1, 2, 3]
for num in more {
var num = num
num += 1
print(num)
}
print(more)
This compiles, and it prints:
2
3
4
[1, 2, 3]
Each num was incremented, but only the copies. The array is unchanged. So there are two problems in the starter code, and renaming or redeclaring the variable fixes only the first one.
Fixing it: loop over the positions
To change an element, you have to write to the array itself, through its position: nums[i] += 1. That means the loop should produce positions, not values. In Swift, 0..<nums.count is a half-open range that runs from 0 up to, but not including, nums.count, which is exactly the set of valid indices.
- Loop over the indices. Replace
for num in numswithfor i in 0..<nums.count. The nameiis now anIntposition. - Read the element by position. The even-or-odd test becomes
nums[i] % 2 == 0. - Write to the element by position. Use
nums[i] += 1in the even branch andnums[i] -= 1in the odd branch. - Leave the declaration alone.
numsmust stay avar. Aletarray can't have its elements assigned.
The solution in Swift
var nums = [1, 2, 3, 4, 5]
for i in 0..<nums.count {
if nums[i] % 2 == 0 {
nums[i] += 1
} else {
nums[i] -= 1
}
}
Printing nums afterwards gives [0, 3, 2, 5, 4]. A few Swift details:
- Arrays are value types.
var b = numsmakes an independent copy, so changingbdoesn't changenums. Here the loop writes tonumsdirectly, so there is nothing to copy back. - Odd numbers stay odd under
%. The remainder takes the sign of the left operand, so-3 % 2is-1, not1. The testnums[i] % 2 == 0is still correct for negative numbers, because it only asks whether the remainder is zero. Testingnums[i] % 2 == 1would wrongly treat negative odd numbers as even. nums.indicesdoes the same job.for i in nums.indicesis an equivalent way to write the range, and it is a good habit because it can't get the upper bound wrong.- An empty array is safe.
0..<0is an empty range, so the body never runs. enumerated()gives both.for (i, v) in nums.enumerated()hands you the position and a copy of the value. You still write throughnums[i], never throughv.
Common mistakes
- Shadowing the loop constant. Adding
var num = numremoves the compile error, but the array still isn't changed. - Writing
0...nums.count. The closed range includesnums.count, which is one past the last valid index, so the program crashes with an index-out-of-range error. - Changing the array's declaration. Declaring
numswithletmakes everynums[i] += 1a compile error. - Swapping the branches. Even numbers go up and odd numbers go down.
[2]becomes[3], and[1]becomes[0].
Key takeaways
- A
for ... inloop variable is aletconstant holding a copy of the element. - To change array elements, loop over the indices and assign through
nums[i]. 0..<nums.countandnums.indicesboth produce exactly the valid positions.
