Scala for
Jakob Jenkov |
The Scala for
loop also executes a certain block of code, as long as a certain condition is true.
However, it works a bit differently than the while loop.
Here is an example:
for(i <- 1 to 10) { println("i is " + i); }
This for loop iterates 10 times and for each iteration assigns the val
i the next number in the
range.
The i <-
construct is called a generator
. For each iteration it initializes the
val
i with a value.
The 1 to 10
is called a Range
type. It returns a range containing the values from
1 to 10, including both numbers. Range types are described in more detail in a different text.
The 1 to 10
is actually a method call that returns the Range
type. It is equivalent
to
(1).to(10);
How this works is described in more detail in the text on Scala's support for domain specific languages (DSL).
Omitting the { } in for Loops
You can omit the { } in for loops, if the body of the loop consists of a single statement. Here is an example:
for(i <- 1 to 10) println("i is " + i);
to vs. until
You can use either the keyword to
or until
when creating a Range
object.
The difference is, that to
includes the last value in the range, whereas until
leaves
it out. Here are two examples:
for(i <- 1 to 10) { println("i is " + i); } for(i <- 1 until 10) { println("i is " + i); }
The first loop iterates 10 times, from 1 to 10 including 10.
The second loop iterates 9 times, from 1 to 9, excluding the upper boundary value 10.
Iterating Collections and Arrays
You can iterate a collection or array using the for loop, like this:
var myArray : Array[String] = new Array[String](10); for(i <- 0 until myArray.length){ myArray(i) = "value is: " + i; } for(value : String <- myArray ) { println(value); }
First an array is created.
Second, each element in the array is initialized to the text "value is: " with the index of the element appended.
Third, the array is iterated using the generator notation ( <-
).
For each iteration the next element in the array is assigned to the val
value
,
and then printed to the console.
Filtering for Loops
In Scala for loops it is possible to apply filtering to the iteration of a collection or array. Here is how:
var myArray : Array[String] = new Array[String](10); for(i <- 0 until myArray.length){ myArray(i) = "value is: " + i; } for(value : String <- myArray if value.endsWith("5")) { println(value); }
Notice the if value.endsWith("5")
marked in bold. This condition, or filter, means that
the for loop only executes its body if the string assigned value
ends with
the text "5". Only one of the array elements ends with the text "5", so the for loop
body is only executed once.
The filtering for loop above is equivalent to the following code:
for(value : String <- myArray) { if value.endsWith("5"){ println(value); } }
Personally, I actually prefer the good old way of writing it (the second way). While the new way is a bit shorter, it is not really much easier to read. Especially not if the conditions are more complex, as you will see hereafter.
Multiple for Loop Filters
You can apply multiple for loop filters like this:
for(value : String <- myArray if value.endsWith("5"); if value.indexOf("value") != -1 ) { println(value); }
Notice the two if-statements inside the for loop declaration. They are separated by a semicolon. Both of these conditions now have to be true, for the for loop body to be executed.
The for loop above is equivalent to this old school for loop:
for(value : String <- myArray) { if( value.endsWith("5") && value.indexOf("value") != -1){ println(value); } }
Personally, I still find the second, old school version easier to read and understand.
Nested Iteration
It is possible to do nested looping in a single for loop. Imagine you had an array of arrays. You could then iterate them like this:
var myArray : Array[Array[String]] = new Array[Array[String]](10); for(i <- 0 until myArray.length){ myArray(i) = new Array[String](10); for(j <- 0 until myArray(i).length){ myArray(i)(j) = "value is: " + i + ", " + j; } } for(anArray : Array[String] <- myArray; aString : String <- anArray ) { println(aString); }
First, an array of arrays is created, and initialized with arrays, and string values.
Second, the array of arrays is iterated using a nested loop. First, each String array is
in the array of arrays is assigned to the val
anArray
.
Then each String value of each String array is assigned to the valu
aString
.
The above nested loop is equivalent to this old school nested for loop:
for(anArray : Array[String] <- myArray) { for(aString : String <- anArray) { println(aString); } }
Again, I still prefer the old school for loop version.
Midstream Variable Binding
It is possible to bind values to a variable in the middle of a nested iteration, like this:
for(anArray : Array[String] <- myArray; aString : String <- anArray; aStringUC = aString.toUpperCase() if aStringUC.indexOf("VALUE") != -1; if aStringUC.indexOf("5") != -1 ) { println(aString); }
The aString.toUpperCase()
result is needed by two filter conditions. Rather than
compute the .toUpperCase()
value twice, by nesting them inside each if-statement,
the uppercase version of aString
is computed just once, and assigned to the variable
aStringUC
. The two filter-conditions (if-statements) can now refer to this "mid stream"
computed value.
You could achieve the same effect using this old school for loop:
for(anArray : Array[String] <- myArray) { for(aString : String <- anArray) { var aStringUC = aString.toUpperCase(); if(aStringUC.indexOf("5") != -1 && aStringUC.indexOf("VALUE") != -1){ println(aString); } } }
Once again, I belive this old school for loop is actually more readable.
Tweet | |
Jakob Jenkov |