Table of Contents
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.
this post is sponsored by my books: | |||
#1 New Release |
FP Best Seller |
Learn Scala 3 |
Learn FP Fast |
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”