Start learning free

LESSON 153 OF 414 · LOOPING OVER ARRAY INDICES

Changing Array Elements in a Scala For Loop

Lesson Read the full explanation

Suppose you have an array nums and your task is to increment every number in that array by one.

The first idea that might come to your mind is to write a for loop like this:

val nums = Array(1, 2, 3)

// Increment items:
for (num <- nums) {
  var copy = num
  copy += 1
}

// Print the array after:
for (num <- nums) {
  println(num)
}

but when you run this code, you'll be surprised to see that the output is:

1
2
3

No element in nums changed its value. How is this possible since we clearly do copy += 1 for every num in nums?


Let's talk about copies:

Take a look at this code:

var number = 100

var number_copy = number

number_copy += 1

println(number_copy) // Output: 101
println(number) // Output: 100

Although we wrote var number_copy = number, this doesn't mean that number and number_copy are one and the same.

number_copy is a completely different entity (stored at a different memory address) than number.

What we did is initialize this new number_copy entity with the value of number. That's why we say the number_copy is a copy of number.

And so number_copy += 1 will only increment number_copy and won't have any effect on number.


The loop variable is just a copy:

This is what also happens with a for loop.

Whenever you write a for loop, the loop variable (in our case num) is just a copy of the current item of the array, not the item itself.

This code

val nums = Array(1, 2, 3)

for (num <- nums) {
  var copy = num
  copy += 1
}

works exactly like this code:

val nums = Array(1, 2, 3)

var num = nums(0)
num += 1

num = nums(1)
num += 1

num = nums(2)
num += 1

Notice that only the variable num is being manipulated, which has nothing to do with the actual entries of the array nums.

You'll see this better if we also print the value of num on each iteration.

val nums = Array(1, 2, 3)

for (num <- nums) {
  val copy = num + 1
  println(copy)
}

// Print the array after:
for (num <- nums) {
  println(num)
}

The output of this code is:

2
3
4
1
2
3

Looping over the indices:

In order to manipulate the entries of some array we have to loop over the items' indices:

val nums = Array(1, 2, 3)

for (index <- nums.indices) {
  nums(index) += 1
}

This code works exactly like this one:

val nums = Array(1, 2, 3)

var index = 0
nums(index) += 1

index += 1
nums(index) += 1

index += 1
nums(index) += 1

In this way we directly manipulate the actual entries of the array (nums(0), nums(1), etc.)

Assignment:

Fix our code such that it correctly increments (adds one to) the even numbers of nums and decrements (subtracts one from) the odd ones.

STEP 1 OF 1
    Ctrl + Enter to check

    SCALA · LOOPING OVER ARRAY INDICES

    Buggy Code Manipulate Array in Scala, explained

    What the code should do

    The starting code has an array nums holding 1, 2, 3, 4, 5. Every even number should go up by one and every odd number should go down by one, so afterwards nums holds 0, 3, 2, 5, 4.

    val nums = Array(1, 2, 3, 4, 5)
    
    for (num <- nums) {
        if (num % 2 == 0) {
            num += 1
        } else {
            num -= 1
        }
    }
    

    This code doesn't even compile. The compiler reports an error on each of the two changing lines:

    value += is not a member of Int
    value -= is not a member of Int
    

    Why it fails

    Two separate problems are hiding in these lines.

    Problem 1: the loop variable is a value, not a variable

    In for (num <- nums), Scala creates num as an immutable value, like a val, and sets it to the current element on each pass. You can't assign to a val. The statement num += 1 is shorthand for num = num + 1, and the compiler only accepts that shorthand when the left side is a var. Since num isn't, it looks for a method named += on Int, doesn't find one, and reports that += is not a member of Int.

    Problem 2: a copy would not reach the array anyway

    Suppose the compiler let you change num. It would still be the wrong thing to change. The loop variable holds a copy of the element's value. Changing a copy leaves the array alone. You can see this by copying into a var, which does compile:

    val nums = Array(1, 2, 3, 4, 5)
    
    for (num <- nums) {
        var copy = num
        copy += 1
    }
    println(nums.mkString(", "))
    
    1, 2, 3, 4, 5
    

    The incremented copy disappears at the end of each pass, and nums is unchanged. Making num mutable would remove the first error and leave you with a program that compiles but does nothing useful.

    The fix: loop over positions

    An array element can be changed through its position. nums(i) = 10 stores 10 at index i, and nums(i) += 1 reads the element, adds one and stores the result back at the same index. So the loop has to produce indices instead of values, and the body works on nums(i).

    1. Loop over the indices. Replace num <- nums with i <- 0 until nums.size. The range 0 until nums.size covers 0 up to the last valid index, nums.size - 1, and leaves out the end value.
    2. Read through the index. The test becomes nums(i) % 2 == 0. Scala indexes arrays with parentheses.
    3. Write through the index. The branches become nums(i) += 1 and nums(i) -= 1. This works on an array element even though nums is declared with val: val fixes which array the name refers to, not what the array contains.

    The solution in Scala

    val nums = Array(1, 2, 3, 4, 5)
    
    for (i <- 0 until nums.size) {
        if (nums(i) % 2 == 0) {
            nums(i) += 1
        } else {
            nums(i) -= 1
        }
    }
    

    After the loop, nums holds 0, 3, 2, 5, 4: 1 became 0, 2 became 3, 3 became 2, 4 became 5 and 5 became 4.

    The same loop can also be written with nums.indices, which is the same range as 0 until nums.size. Either form is fine.

    Gotchas

    • val versus var on the array. val nums = Array(...) is fine for this task. An array is mutable and fixed in size, so you can replace its elements but you can't add or remove any. Only nums = Array(7, 8) would be rejected, because that reassigns the name.
    • Looping over elements still helps for reading. If you only need to look at the values, as when summing them or printing them, for (num <- nums) is the cleaner form. Use indices when you need to store something back.
    • Immutable collections behave differently. A List can't be changed in place at all. To get a changed list you build a new one, for example with map: List(1, 2, 3).map(_ + 1) returns List(2, 3, 4) and leaves the original list as it was.
    • Going out of range. An index of nums.size or larger throws an ArrayIndexOutOfBoundsException. Ending the range at nums.size with until avoids it, whereas 0 to nums.size would include that bad index.

    Key takeaways

    • The loop variable in for (x <- array) is an immutable copy of an element.
    • To change the elements of an array, loop over its indices and assign to array(i).
    • += on an Int only compiles when the target is a var or an array slot.
    Keep going with ScalaAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free