How to loop over a Scala collection with a ‘for’ loop

This is an excerpt from the 1st Edition of the Scala Cookbook (partially modified for the internet). This is Recipe 10.10, “How to Loop over a Scala Collection with a for Loop”

Problem

You want to loop over the elements in a collection using a Scala for loop, possibly creating a new collection from the existing collection using the for/yield combination.

Solution

You can loop over any Traversable type (basically any sequence) using a for loop:

scala> val fruits = Traversable("apple", "banana", "orange")
fruits: Traversable[String] = List(apple, banana, orange)

scala> for (f <- fruits) println(f)
apple
banana
orange

scala> for (f <- fruits) println(f.toUpperCase)
APPLE
BANANA
ORANGE

If your algorithm is long, perform the work in a block following a for loop:

scala> val fruits = Array("apple", "banana", "orange")
fruits: Array[String] = Array(apple, banana, orange)

scala> for (f <- fruits) {
     |     // imagine this required multiple lines
     |     val s = f.toUpperCase
     |     println(s)
     | }
APPLE
BANANA
ORANGE

This example shows one approach to using a counter inside a for loop:

scala> for (i <- 0 until fruits.size) println(s"element $i is ${fruits(i)}")
element 0 is apple
element 1 is banana
element 2 is orange

You can also use the zipWithIndex method when you need a loop counter:

scala> for ((elem, count) <- fruits.zipWithIndex) {
     |     println(s"element $count is $elem")
     | }
element 0 is apple
element 1 is banana
element 2 is orange

When using zipWithIndex, consider calling view before zipWithIndex:

// added a call to 'view'
for ((elem, count) <- fruits.view.zipWithIndex) {
    println(s"element $count is $elem")
}

See the next recipe for details.

Using zip with a Stream is another way to generate a counter:

scala> for ((elem,count) <- fruits.zip(Stream from 1)) {
     |     println(s"element $count is $elem")
     | }
element 1 is apple
element 2 is banana
element 3 is orange

See the next recipe for details on using zipWithIndex and zip to create loop counters.

If you just need to do something N times, using a Range works well:

scala> for (i <- 1 to 3) println(i)
1
2
3

In that example, the expression 1 to 3 creates a Range, which you can demonstrate in the REPL:

scala> 1 to 3
res0: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3)

Again you can use a block inside curly braces when your algorithm gets long:

scala> for (i <- 1 to 3) {
     |     // do whatever you want in this block
     |     println(i)
     | }
1
2
3

The for/yield construct

The previous examples show how to operate on each element in a sequence, but they don’t return a value. As with the foreach examples in the previous recipe, they’re used for their side effect.

To build a new collection from an input collection, use the for/yield construct. The following example shows how to build a new array of uppercase strings from an input array of lowercase strings:

scala> val fruits = Array("apple", "banana", "orange")
fruits: Array[java.lang.String] = Array(apple, banana, orange)

scala> val newArray = for (e <- fruits) yield e.toUpperCase
newArray: Array[java.lang.String] = Array(APPLE, BANANA, ORANGE)

The for/yield construct returns (yields) a new collection from the input collection by applying your algorithm to the elements of the input collection, so the array newArray contains uppercase versions of the three strings in the initial array. Using for/yield like this is known as a for comprehension.

I’ll write the “for comprehension” as “for-comprehension” in this document to make it easier to read.

If your for/yield processing requires multiple lines of code, perform the work in a block after the yield keyword:

scala> val newArray = for (fruit <- fruits) yield {
     |     // imagine this required multiple lines
     |     val upper = fruit.toUpperCase
     |     upper
     | }
newArray: Array[java.lang.String] = Array(APPLE, BANANA, ORANGE)

If your algorithm is long, or you want to reuse it, first define it in a method (or function):

def upperReverse(s: String) = {
    // imagine this is a long algorithm
    s.toUpperCase.reverse
}

then use the method with the for/yield loop:

scala> val newArray = for (fruit <- fruits) yield upperReverse(fruit)
newArray: Array[String] = Array(ELPPA, ANANAB, EGNARO)

Using a for loop with a Map

You can also iterate over a Map nicely using a for loop:

scala> val names = Map("fname" -> "Ed", "lname" -> "Chigliak")
names: scala.collection.immutable.Map[String,String] = Map(fname -> Ed, lname -> Chigliak)

scala> for ((k,v) <- names) println(s"key: $k, value: $v")
key: fname, value: Ed
key: lname, value: Chigliak

See Recipe 11.18, “Traversing a Map”, for more examples of iterating over a Map.

Discussion

When using a for loop, the <- symbol can be read as “in,” so the following statement can be read as “for i in 1 to 3, do ...”:

for (i <- 1 to 3) { // more code here ...

As demonstrated in Recipe 3.3, “Using a for Loop with Embedded if Statements (Guards)”, you can also combine a for loop with if statements, which are known as guards:

for {
    file <- files
    if file.isFile
    if file.getName.endsWith(".txt")
} doSomething(file)

See that recipe for more examples of using guards with for loops.

See Also

  • Recipe 3.3, “Using a for Loop with Embedded if Statements (Guards)”
  • Recipe 10.9, “Looping over a Collection with foreach”
  • Recipe 10.13, “Transforming One Collection to Another with for/yield”