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

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

Back to top

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.

Back to top

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
Back to top

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)
Back to top

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.

Back to top

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.

Back to top

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”
Back to top

The Scala Cookbook

This tutorial is sponsored by the Scala Cookbook, which I wrote for O’Reilly:

You can find the Scala Cookbook at these locations:

Back to top

Add new comment

The content of this field is kept private and will not be shown publicly.

Anonymous format

  • Allowed HTML tags: <em> <strong> <cite> <code> <ul type> <ol start type> <li> <pre>
  • Lines and paragraphs break automatically.
By submitting this form, you accept the Mollom privacy policy.