Common Data Structures

We'll dive into Scala collections later, but to get started, we'll mention a few now.

There are three types of collections: sequences, sets, and maps. These can further be categorized as mutable (we can add and remove members) and immutable.

Collections are generic, parameterized by the type of element they contain. For example:

Array[Int] // = the type of all arrays containing integers.

We can think of a sequence as a function that takes a position as input and returns the element in the sequence at that position:

myArray(0) // = first element of myArray

If the sequence members are variables, we can update the element at a position with syntax like this:

myArray(0) = 100 // update variable at position 0

Scala collections methods are very uniform. They all have pretty much the same methods and we can iterate over them using a for-loops:

for(i <- myArray) ...

Ranges

A range is a bounded subsequence of a sequence or ordered type. We have seen ranges used with the for-loop:

scala> for(d <- 0 to 9) print(d)
0123456789
scala> for(d <- 0 until 9) print(d)
012345678
scala> for(d <- 0 to 9 by 2) print(d)
02468
scala> for(d <- 9 to 0 by -1) print(d)
9876543210

We can use sequences in other ways:

val digits = 0 to 9
def isDigit(n: Int) = digits contains n // same as digits.contains(n)

scala> isDigit(5)
val res7: Boolean = true

scala> isDigit(10)
val res8: Boolean = false

Arrays

An array is an indexed sequence of contiguous variables.

Creating an array of values is simple:

val days = Array("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday")

Like all sequences, we can iterate over arrays:

scala> for(day <- days) println(day)
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
Sunday

Or we can iterate over the indices of an array:

scala> for(i <- 0 until days.length) println(days(i))   
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
Sunday

Notice that we use parentheses instead of square braces to access array elements.

Even though days is a constant array, its components are still variables that can be updated:

scala> days(2) = "Hump Day"
scala> days
res19: Array[String] = Array(Monday, Tuesday, Hump Day, Thursday, Friday, Saturday, Sunday)

We can declare and fill an array in two steps:

var squares = Array.ofDim[Int](10)
for(i <- 0 until 10) squares(i) = i * i  

scala> for(square <- squares) println(square)
0
1
4
9
16
25
36
49
64
81

We can do lots of things to arrays (see the Scala docs on this). For example, we can add an element to the end of an array:

scala> squares :+ 100
val res15: Array[Int] = Array(0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100)

scala> squares
val res16: Array[Int] = Array(0, 1, 4, 9, 16, 25, 36, 49, 64, 81)

scala> squares = squares :+ 100
squares: Array[Int] = Array(0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100)

scala> squares
val res17: Array[Int] = Array(0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100)

Notice that the first expression didn't change squares. That's because arrays are immutable. But because squares is a variable containing a pointer to an array we can have it point to a new array.

Strings

A string is an immutable sequence of characters. Scala strings are Java strings with a few extra features that will be introduced later.

Example: Pig Latin Translator

·       Translator.scala

A sample session:

-> apple
appleay
-> peach
eachpay
-> 100
100
-> quit
uitqay
-> !quit
bye

Example: A simple Calculator

·       Calculator.scala

A sample session:

-> add 12 25
37.0
-> mul 3.2 4
12.8
-> div 4 2
Unrecognized operator: div
-> add 2 3 4
command syntax: OP NUM NUM
-> add 2 ten
For input string: "ten"
-> quit
bye

Tuples

A tuple is a sequence of values:

scala> val record = ("Carl Larc", "male", 25)
record: (String, String, Int) = (Carl Larc,male,25)

We can access the components of a tuple:

scala> record(0)
res31: String = Carl Larc

scala> record(1)
res32: String = male

scala> record(2)
res33: Int = 25

scala> record(3)
ERROR

A cool feature is a compound declaration that extracts the components of a tuple and assigns them to variables or constants:

scala> val (name, gender, age) = record
name: String = Carl Larc
gender: String = male
age: Int = 25

scala> name
res28: String = Carl Larc

scala> gender
res29: String = male

scala> age
res30: Int = 25

Tuples are useful for functions that need to return more than one value:

def toPolar(crt: (Double, Double)) = {
  val (xc, yc) = crt
  val r = math.sqrt(xc * xc + yc * yc)
  val theta = math.acos(xc/yc)
  (r, theta)
}                                           
  
toPolar((1, 1))  // = (1.4142135623730951,0.0)
toPolar((0, 1))  // = (1.0,1.5707963267948966)

Maps

A map is a collection of key-value pairs, usually implemented as a tree or hash table.

val gender = Map(1->"male", 2->"female", 3->"transgender", 4->"unspecified")

scala> gender(3)
res0: String = transgender

scala> gender(0)
java.util.NoSuchElementException: key not found: 0

By default, maps are immutable. This means that once created, pairs can't be added, removed, or modified:

scala> gender(1) = "man"
<console>:9: error: value update is not a member of scala.collection.immutable.Map[Int,String]

Here's how to declare a mutable map:

val scores = collection.mutable.Map("smith"->90, "jones"->86, "simpson"->67)
 

scores("smith")    // = 90
scores("jones")    // = 86
scores("hanson")   // key not found: hanson
 

Oops, we forgot poor Hanson. Not a problem for mutable maps:

scores += "hanson"->100
scores("hanson")   // = 100
 

If a student drops the course, we can delete them from the map:

scores -= "jones"     

Smith is begging for 5 more points, here's how we handle it:

scores("smith") = scores("smith") + 5

Example: Concordance

The following code shows how to build a concordance from a text file. A concordance is a map of type Map[String, Int]. A member such as ("gadzooks", 3) says that the word "gadzooks" occurred three times in the document.

import scala.io.Source

object Concordance extends App {
  var fTable = new scala.collection.mutable.HashMap[String, Int] // our frequencey table
  val wordPattern = "[a-zA-Z]+".r // this is how to turn a string into a regular expression
  val source = Source.fromFile(args(0), "UTF-8") // source file specified in command line
  val lineIterator = source.getLines
  for(line <- lineIterator) {
    for(nextWord <- wordPattern.findAllIn(line)) {
      fTable(nextWord.toLowerCase) = fTable.getOrElse(nextWord.toLowerCase, 0) + 1
    }
  }
  for((k, v) <- fTable) println(k + ": " + v)
}

Notes:

table.getOrElse(key, default) = default if no value is associated with key

Here's a document:

to be or not to be,
that is the question.
A question to be sure.
I sure am thirsty.
May I ask for some ale?
Hello?
Was that the wrong question to ask?

Here's the concordance produced:

wrong: 1
am: 1
is: 1
not: 1
ask: 2
some: 1
or: 1
question: 3
be: 3
to: 4
sure: 2
that: 2
thirsty: 1
for: 1
a: 1
was: 1
ale: 1
the: 2
i: 2
hello: 1
may: 1

Enumerations

Enumerations are new to Scala 3.0. Here's an example:

enum Heading {
  case north, south, east, west
}

There are several cool features of Scala enumerations:

scala> Heading.north.ordinal
val res18: Int = 0

scala> Heading.fromOrdinal(2)
val res19: Heading = east

scala> Heading.values
val res20: Array[Heading] = Array(north, south, east, west)

scala> Heading.valueOf("west")
val res21: Heading = west

Break Blocks

Although break and continue aren't in the Scala, a more general form of break can be imported from the scala library:

import scala.util.control.Breaks._

object TestBreak {
   def main(args: Array[String]): Unit = {
     println("entering main")
     breakable {
       println("A")
       breakable {
         println("B")
         break
         println("C")
       }
       println("D")
       break
       println("E")
     }
     println("exiting main")
   }
}

Output:

entering main
A
B
D
exiting main