The idea: the loop variable is not the element
A common first attempt at "add one to every number in the array" is a for loop that increments the loop variable:
val nums = intArrayOf(1, 2, 3)
for (num in nums) {
num++
}
In Kotlin this code does not even compile. The compiler reports:
error: 'val' cannot be reassigned.
Two facts explain it. First, the variable in for (num in nums) is a read-only val, so any assignment to it, including num++ and num--, is rejected. Second, even if Kotlin allowed the change, it would not reach the array. On each iteration num receives a copy of the current element, and a copy lives on its own.
Copies in Kotlin
Copying an Int into a new variable gives you two independent values:
val number = 100
var numberCopy = number
numberCopy++
println(numberCopy)
println(number)
101
100
Incrementing numberCopy leaves number alone. A for ... in loop does the same thing for each element. This loop:
for (num in nums) {
println(num)
}
behaves like this sequence of assignments, where num is a fresh value each time and nums itself is only read:
val num0 = nums[0]
val num1 = nums[1]
val num2 = nums[2]
Reading elements this way is fine whenever you only need their values: summing, searching, printing. To change the array, you need a different loop.
Looping over the indices
The elements of an array are reached through their positions. If the loop variable is an index, you can write to nums[index] directly:
val nums = intArrayOf(1, 2, 3)
for (index in 0 until nums.size) {
nums[index]++
}
println(nums.joinToString())
2, 3, 4
Here the loop variable is the index, which is only read, and the thing that changes is nums[index], an element stored in the array. nums[index]++ is allowed because nums is an IntArray, and the elements of an array are mutable even when the variable holding the array is a val. The val means nums always refers to the same array, not that the array's contents are frozen.
Some equivalent ways to get the indices:
0 until nums.sizecounts from 0 up to, but not including,nums.size.nums.indicesis the same range, ready-made. Most Kotlin code uses it.nums.withIndex()gives index and value together:for ((i, v) in nums.withIndex()) nums[i] = v + 1.
The exercise
The starting code should turn nums = [1, 2, 3, 4, 5] into [0, 3, 2, 5, 4]: add one to every even number and subtract one from every odd number.
val nums = intArrayOf(1, 2, 3, 4, 5)
for (num in nums) {
if (num % 2 == 0) {
num++
} else {
num--
}
}
It fails to compile with two errors, one for num++ and one for num--. Both come from the loop variable being read-only. The fix has three parts:
- Loop over positions. Replace
for (num in nums)with a loop over the indices, so that the loop variable,i, is a position. - Read through the index. The even-or-odd test becomes
nums[i] % 2 == 0, which asks about the element at positioni. - Write through the index.
nums[i]++andnums[i]--change the element stored in the array.
The declaration of nums stays as it is.
val nums = intArrayOf(1, 2, 3, 4, 5)
for (i in 0 until nums.size) {
if (nums[i] % 2 == 0) {
nums[i]++
} else {
nums[i]--
}
}
Printing nums.joinToString() afterwards gives 0, 3, 2, 5, 4. Walking through it: 1 is odd, so it becomes 0; 2 is even, so it becomes 3; 3 becomes 2; 4 becomes 5; 5 becomes 4.
Common mistakes
- Modifying the loop variable. It never compiles in Kotlin, and even an allowed change would affect only a copy.
- Going past the end. Using
0..nums.sizeincludesnums.sizeitself, andnums[nums.size]throwsArrayIndexOutOfBoundsException. Useuntilorindices. - Trying the same on a read-only list. A
Listcreated withlistOfhas no way to assign by index:list[0] = 5is rejected becauseListhas nosetoperator. Use an array, or aMutableListfrommutableListOf. - Thinking objects behave differently. For an array of objects, the loop variable holds a reference to the same object, so changing the object's properties through it does show up in the array. What you cannot do is replace the element itself. Replacing needs the index:
names[i] = names[i].uppercase().
Key takeaways
- The variable in
for (x in array)is a read-only copy of the current element. - To change elements, loop over
array.indices(or0 until array.size) and assign toarray[i]. - A
valarray can still have its elements changed; only the reference is fixed.