Topics in This Chapter A1
In this chapter, you will learn how to work with arrays in Scala. Java, JavaScript, Python, and C++ programmers usually choose an array or its close relation (such as lists or vectors) when they need to collect a bunch of elements. In Scala, there are other choices (see Chapter 13), but for now, I’ll assume you are impatient and just want to get going with arrays.
Key points of this chapter:
Use an Array
if the length is fixed, and an ArrayBuffer
if the length can vary.
Use ()
to access elements.
Use for elem <- arr do ...
to traverse the elements.
Use for elem <- arr yield ...
to transform into a new array.
Scala and Java arrays are interoperable; with ArrayBuffer
, use scala.jdk.CollectionConverters
.
If you need an array whose length doesn’t change, use the Array
type in Scala. For example,
val strings = Array("Hello", "World")
//
An Array[String] of length 2—the type is inferredval moreStrings = Array.ofDim[String](5)
//
A string array with five elements, all initialized with nullval nums = Array.ofDim[Int](10)
//
An array of ten integers, all initialized with zero
Note
In Java, arrays are created with the new
operator. However, in Scala 3, the use of the new
operator is discouraged, as you will see in Chapter 5. It is possible to call new Array[Int](10)
to create an array of ten integers. But it is also a bit confusing since Array[Int](10)
(without the new
) is an array of length 1, containing the integer 10.
Use the ()
operator to access or modify the elements of an array:
strings(0) = "Goodbye"
//
strings is now Array("Goodbye", "World")
Why not []
as in Java, JavaScript, C++, or Python? Scala takes the point of view that an array is like a function that maps the index values to the elements.
Inside the JVM, a Scala Array
is implemented as a Java array. The arrays in the preceding example have the type java.lang.String[]
inside the JVM. An array of Int
, Double
, or another equivalent of the Java primitive types is a primitive type array. For example, Array(2,3,5,7,11)
is an int[]
in the JVM.
Java has ArrayList
, Python has list
, and C++ has vector
for arrays that grow and shrink on demand. The equivalent in Scala is the ArrayBuffer
.
import scala.collection.mutable.ArrayBuffer
val b = ArrayBuffer[Int]()
//
An empty array buffer, ready to hold integersb += 1
// ArrayBuffer(1)
//
Add an element at the end with +=b ++= Array(1, 2, 3, 5, 8)
// ArrayBuffer(1, 1, 2, 3, 5, 8)
//
You can append any collection with the ++= operatorb.dropRightInPlace(3)
// ArrayBuffer(1, 1, 2)
//
Removes the last three elements
Adding or removing elements at the end of an array buffer is an efficient (“amortized constant time”) operation.
You can also insert and remove elements at an arbitrary location, but those operations are not as efficient—all elements after that location must be shifted. For example:
b.insert(2, 6)
// ArrayBuffer(1, 1, 6, 2)
//
Inserts before index 2b.insertAll(2, Array(7, 8, 9))
// ArrayBuffer(1, 1, 7, 8, 9, 6, 2)
//
Inserts the elements from another collectionb.remove(2)
// ArrayBuffer(1, 1, 8, 9, 6, 2)
b.remove(2, 3)
// ArrayBuffer(1, 1, 2)
//
The second parameter tells how many elements to remove
Sometimes, you want to build up an Array
, but you don’t yet know how many elements you will need. In that case, first make an array buffer, then call
b.toArray
// Array(1, 1, 2)
Conversely, call a.toBuffer
to convert the array a
to an array buffer.
In Java and C++, there are several syntactical differences between arrays and array lists/vectors. Scala is much more uniform. Most of the time, you can use the same code for both.
Here is how you traverse an array or array buffer with a for
loop:
for i <- 0 until a.length do
println(s"$i: ${a(i)}")
The until
method is similar to the to
method, except that it excludes the last value. Therefore, the variable i
goes from 0
to a.length - 1
.
In general, the construct
for i <-
rangedo
makes the variable i
traverse all values of the range. In our case, the loop variable i
assumes the values 0
, 1
, and so on until (but not including) a.length
.
To visit every second element, let i
traverse
0 until a.length by 2
// Range(0, 2, 4, ...)
To visit the elements starting from the end of the array, traverse
a.length -1 to 0 by -1
// Range(..., 2, 1, 0)
Tip
Instead of 0 until a.length
or a.length -1 to 0 by -1
, you can use a.indices
or a.indices.reverse
.
If you don’t need the array index in the loop body, visit the array elements directly:
for elem <- a do
println(elem)
This is similar to the “enhanced” for
loop in Java, the “for in” loop in JavaScript or Python, or the “range-based” for
loop in C++. The variable elem
is set to a(0)
, then a(1)
, and so on.
In the preceding sections, you saw how to work with arrays just like you would in other programming languages. But in Scala, you can go further. It is easy to take an array (or array buffer) and transform it in some way. Such transformations don’t modify the original array but yield a new one.
Use a for
comprehension like this:
val a = Array(2, 3, 5, 7, 11)
val result = for elem <- a yield 2 * elem
//
result is Array(4, 6, 10, 14, 22)
The for
/yield
loop creates a new collection of the same type as the original collection. If you started with an array, you get another array. If you started with an array buffer, that’s what you get from for
/yield
.
The result contains the expressions after the yield
, one for each iteration of the loop.
Oftentimes, when you traverse a collection, you only want to process the elements that match a particular condition. This is achieved with a guard: an if
inside the for
. Here we double every even element, dropping the odd ones:
for elem <- a if elem % 2 == 0 yield elem / 2
Keep in mind that the result is a new collection—the original collection is not affected.
Note
Some programmers with experience in functional programming prefer map
and filter
to for
/yield
and guards:
a.filter(_ % 2 == 0).map(_ / 2)
That’s just a matter of style—the for
/yield
loop does exactly the same work. Use whichever you find easier.
Suppose we want to remove all negative elements from an array buffer of integers. A traditional sequential solution might traverse the array buffer and remove unwanted elements as they are encountered.
var n = b.length
var i = 0
while i < n do
if b(i) >= 0 then i += 1
else
b.remove(i)
n -= 1
That’s a bit fussy—you have to remember not to increment i
when you remove the element, and to decrement n
instead. It is also not efficient to remove elements from the middle of the array buffer. This loop unnecessarily moves elements that will later be removed.
In Scala, the obvious solution is to use a for
/yield
loop and keep all non-negative elements:
val nonNegative = for elem <- b if elem >= 0 yield elem
The result is a new array buffer. Suppose that we want to modify the original array buffer instead, removing the unwanted elements. Then we can collect their positions:
val positionsToRemove = for i <- b.indices if b(i) < 0 yield i
Now remove the elements at those positions, starting from the back:
for i <- positionsToRemove.reverse do b.remove(i)
Alternatively, remember the positions to keep, copy them over, and then shorten the buffer:
val positionsToKeep = for i <- b.indices if b(i) >= 0 yield i
for j <- positionsToKeep.indices do b(j) = b(positionsToKeep(j))
b.dropRightInPlace(b.length - positionsToKeep.length)
The key observation is that it is better to have all index values together instead of seeing them one by one.
It is often said that a large percentage of business computations are nothing but computing sums and sorting. Fortunately, Scala has built-in functions for these tasks.
Array(1, 7, 2, 9).sum
// 19
//
Works for ArrayBuffer too
In order to use the sum
method, the element type must be a numeric type: either an integral or floating-point type or BigInteger/BigDecimal
.
Similarly, the min
and max
methods yield the smallest and largest element in an array or array buffer.
ArrayBuffer("Mary", "had", "a", "little", "lamb").max
// "little"
The sorted
method sorts an array or array buffer and returns the sorted array or array buffer, without modifying the original:
val b = ArrayBuffer(1, 7, 2, 9)
val bSorted = b.sorted
//
b is unchanged; bSorted is ArrayBuffer(1, 2, 7, 9)
You can also supply a comparison function, but then you should use the sortWith
method:
val bDescending = b.sortWith(_ > _) // ArrayBuffer(9, 7, 2, 1)
See Chapter 12 for the function syntax.
You can sort an array or array buffer in place:
val a = Array(1, 7, 2, 9)
a.sortInPlace()
//
a is now Array(1, 2, 7, 9)
For the min
, max
, and sortInPlace
methods, the element type must have a comparison operation. This is the case for numbers, strings, and other types with a “given” Ordering
object.
Finally, if you want to display the contents of an array or array buffer, the mkString
method lets you specify the separator between elements. A second variant has parameters for the prefix and suffix. For example,
a.mkString(" and ")
// "1 and 2 and 7 and 9"
a.mkString("<", ",", ">")
// "<1,2,7,9>"
Contrast with toString
:
a.toString
// "[I@b73e5"
//
This is the useless toString method for arrays, straight from Javab.toString
// "ArrayBuffer(1, 7, 2, 9)"
There are lots of useful methods on arrays and array buffers, and it is a good idea to browse the Scala documentation to get an idea of what’s there.
Since the Array
class compiles directly to a Java array, most of the useful array methods are found in the ArrayOps
and ArraySeq
classes.
Scala has a rich type system, so you may encounter some strange-looking syntax as you browse the Scala documentation. Fortunately, you don’t have to understand all nuances of the type system to do useful work. Use Table 3–1 as a “decoder ring.”
For now, you’ll have to skip methods that use types such as Option
or PartialFunction
that we haven’t discussed yet.
Table 3–1 Scaladoc Decoder Ring
Scaladoc | Explanation |
---|---|
| This method takes a predicate, a function from |
| Ignore the |
| The |
| This method computes a potentially large result and therefore returns it as an iterator instead of a collection, so that you can visit the elements one by one instead of placing them in a collection. For now, just call |
| Note that the function copies an At first reading, just ignore the |
| The element type |
| This method concatenates two arrays. The second array can be a subtype of the first. The “implicit” of type |
| Here, you just have to admit defeat. This method is required for a technical reason that is only of interest to the Scala library implementors. |
Like in Java, JavaScript, or Python, multidimensional arrays are implemented as arrays of arrays. For example, a two-dimensional array of Double
values has the type Array[Array[Double]]
. To construct such an array, use the ofDim
method:
val matrix = Array.ofDim[Double](3, 4) //
Three rows, four columns
To access an element, use two pairs of parentheses:
matrix(row)(column) = 42
You can make ragged arrays, with varying row lengths:
val triangle = Array.ofDim[Array[Int]](10)
for i <- triangle.indices do
triangle(i) = Array.ofDim[Int](i + 1)
Since Scala arrays are implemented as Java arrays, you can pass them back and forth between Java and Scala.
This works in almost all cases, except if the array element type isn’t an exact match. In Java, an array of a given type is automatically converted to an array of a supertype. For example, a Java String[]
array can be passed to a method that expects a Java Object[]
array. Scala does not permit this automatic conversion because it is unsafe. (See Chapter 17 for a detailed explanation.)
Suppose you want to invoke a Java method with an Object[]
parameter, such as java.util.Arrays.binarySearch(Object[] a, Object key)
:
val a = Array("Mary", "a", "had", "lamb", "little")
java.util.Arrays.binarySearch(a, "beef") //
Does not work
This does not work because Scala will not convert an Array[String]
into an Array[Object]
. You can force the conversion like this:
java.util.Arrays.binarySearch(a.asInstanceOf[Array[Object]], "beef")
Note
This is just an example to show how to overcome element type differences. If you want to carry out binary search in Scala, do it like this:
import scala.collection.Searching.*
val result = a.search("beef")
The result is Found(n)
if the element was found at position n
or InsertionPoint(n)
if the element was not found but should be inserted before position n
.
If you call a Java method that receives or returns a java.util.List
, you could, of course, use a Java ArrayList
in your Scala code—but that is unattractive. Instead, import the conversion methods in scala.jdk.CollectionConverters
. Then you can call the asJava
method to convert any sequence (such as a Scala buffer) into a Java list.
For example, the java.lang.ProcessBuilder
class has a constructor with a List<String>
parameter. Here is how you can call it from Scala:
import scala.jdk.CollectionConverters.*
import scala.collection.mutable.ArrayBuffer
val command = ArrayBuffer("ls", "-al", "/usr/bin")
val pb = ProcessBuilder(command.asJava) //
Scala to Java
The Scala buffer is wrapped into an object of a Java class that implements the java.util.List
interface.
Conversely, when a Java method returns a java.util.List
, you can convert it into a Buffer
:
import scala.jdk.CollectionConverters.*
import scala.collection.mutable.Buffer
val cmd: Buffer[String] = pb.command().asScala //
Java to Scala//
You can’t use ArrayBuffer—the wrapped object is only guaranteed to be a Buffer
If the Java method returns a wrapped Scala buffer, then the implicit conversion unwraps the original object. In our example, cmd == command
.
1. Write a code snippet that sets a
to an array of n
random integers between 0
(inclusive) and n
(exclusive).
2. Write a loop that swaps adjacent elements of an array of integers. For example, Array(1, 2, 3, 4, 5)
becomes Array(2, 1, 4, 3, 5)
.
3. Repeat the preceding assignment, but produce a new array with the swapped values. Use for
/yield
.
4. Given an array of integers, produce a new array that contains all positive values of the original array, in their original order, followed by all values that are zero or negative, in their original order.
5. How do you compute the average of an Array[Double]
?
6. How do you rearrange the elements of an Array[Int]
so that they appear in reverse sorted order? How do you do the same with an ArrayBuffer[Int]
?
7. Write a code snippet that produces all values from an array with duplicates removed. (Hint: Look at Scaladoc.)
8. Suppose you are given an array buffer of integers and want to remove all but the first negative number. Here is a sequential solution that sets a flag when the first negative number is called, then removes all elements beyond.
var first = true
var n = a.length
var i = 0
while i < n do
if a(i) >= 0 then i += 1
else
if first then
first = false
i += 1
else
a.remove(i)
n -= 1
This is a complex and inefficient solution. Rewrite it in Scala by collecting positions of the negative elements, dropping the first element, reversing the sequence, and calling a.remove(i)
for each index.
9. Improve the solution of the preceding exercise by collecting the positions that should be moved and their target positions. Make those moves and truncate the buffer. Don’t copy any elements before the first unwanted element.
10. Make a collection of all time zones returned by java.util.TimeZone.getAvailableIDs
that are in America. Strip off the "America/"
prefix and sort the result.
11. Import java.awt.datatransfer.*
and make an object of type SystemFlavorMap
with the call
val flavors = SystemFlavorMap.getDefaultFlavorMap().asInstanceOf[SystemFlavorMap]
Then call the getNativesForFlavor
method with parameter DataFlavor.imageFlavor
and get the return value as a Scala buffer. (Why this obscure class? It’s hard to find uses of java.util.List
in the standard Java library.)