The latest instance of the course can be found at: O1: 2024
Luet oppimateriaalin englanninkielistä versiota. Mainitsit kuitenkin taustakyselyssä osaavasi suomea. Siksi suosittelemme, että käytät suomenkielistä versiota, joka on testatumpi ja hieman laajempi ja muutenkin mukava.
Suomenkielinen materiaali kyllä esittelee englanninkielisetkin termit. Myös suomenkielisessä materiaalissa käytetään ohjelmaprojektien koodissa englanninkielisiä nimiä kurssin alkupään johdantoesimerkkejä lukuunottamatta.
Voit vaihtaa kieltä A+:n valikon yläreunassa olevasta painikkeesta. Tai tästä: Vaihda suomeksi.
Chapter 1.5: Collections and References
About This Page
Questions Answered: How do I store information that consists of multiple related elements? How can I refer to such a collection of elements from multiple locations within a program?
Topics: The basics of using collections in general and buffers in particular. References.
What Will I Do? Program in the REPL and read.
Rough Estimate of Workload:? About an hour.
Points Available: A20.
Related Projects: None.
Introduction: Collections of Elements
Most computer programs manipulate numerous granules of information that are related to each other in some way. For instance, a program might need to keep track of multiple scientific measurements, multiple hotel experiences recorded by the user, multiple friends of the user, or multiple students enrolled in a course.
For such purposes, we use collections (kokoelma). A collection can contain, for instance, all the friends of a particular user or a sequence of measurements. A value stored in a collection is called an element (alkio) of the collection; an element could be a single measurement, a single experience, or a single person, for example.
There are different kinds of collections. Since the need for collections is so universal, programming languages provide a selection of ready-made collection types for the programmer to pick and choose from. Scala offers a plentiful selection of collection types, of which we’ll initially use just one.
Creating a Buffer
One of Scala’s collection types is called the buffer (puskuri). A buffer stores its elements in a specific order. You can add new elements to a buffer, remove elements that were previously added, or replace elements with new ones. Informally, you can think of a buffer as a sort of editable list of items stored in computer memory.
To get started, let’s try creating a buffer that contains four strings as its elements. Here is one, albeit long-winded, way of creating a buffer in Scala:
scala.collection.mutable.Buffer("the first element", "second", "third", "and a fourth")res0: scala.collection.mutable.Buffer[String] = ArrayBuffer(the first element, second, third, and a fourth)
Buffer[String]
.
In Scala, square brackets mark type parameters (tyyppiparametri)
that further specify the data type. In our case, the type
parameter is String
, so what we have here isn’t just any
buffer, but a buffer that contains strings. You can read
Buffer[String]
as “buffer of string”.ArrayBuffer
:
a buffer that is internally implemented using a particular
construct (arrays). In Scala, buffers are ArrayBuffer
s by
default, but that’s not something we need to further concern
ourselves with at this point.So. We managed to create a buffer, but it sure was unlovely to have to write the entire name of the package in the buffer-creating command. If we want to create multiple buffers, it seems useless to keep repeating the package name. Fortunately, we already have the cure for this verbosity.
More on the import
Command and Packages
Above, we used the fully qualified name scala.collection.mutable.Buffer
, which is a
combination of the package name scala.collection.mutable
and the specific name Buffer
.
However, we can use the import
command of Chapter 1.3 to trumpet our intention to use
buffers in advance:
import scala.collection.mutable.Bufferimport scala.collection.mutable.Buffer
Now we can create buffers by simply using the word Buffer
.
Buffer("the first element", "second", "third", "and a fourth")res1: scala.collection.mutable.Buffer[String] = ArrayBuffer(the first element, second, third, and a fourth) Buffer(2.40, 3.11, 4.56, 10.29, 8.11)res2: scala.collection.mutable.Buffer[Double] = ArrayBuffer(2.4, 3.11, 4.56, 10.29, 8.11)
In the rest of the examples in this chapter, we’re going to assume that import
scala.collection.mutable.Buffer
has been issued.
The special package scala
The data types that we used earlier (Int
, Double
, String
) have “just worked”. Why
didn’t we need to import them like we needed to import the buffer type?
Int
, Double
, and String
, too, have been defined in a package that’s part of the
standard Scala toolkit. And not just in any package but in the single most significant
package of them all, called simply scala
. The fully qualified name of the Int
type,
for instance, is scala.Int
. We don’t need to use the full name, however, because the
contents of the special package scala
is so universally useful and so inseparable from
the Scala language itself that it’s been made automatically available to us in all Scala
programs.
About the REPL examples in this ebook
Note: As shown above, the REPL reports the buffer’s type
thoroughly, using a fully qualified name such as scala.collection.mutable.Buffer[Double]
.
In practice, we know full well that we’re using the buffer type
defined in that package, and gain nothing from repeatedly reading
that in the REPL’s reports. For your reading convenience, future
examples in this ebook slightly simplify the REPL’s outputs here
and there; for instance, you may see simply Buffer[Double]
instead
of scala.collection.mutable.Buffer[Double]
. So don’t be flurried
if the printouts in the ebook don’t exactly match what you get
when you enter the same commands in the actual REPL.
Another note: The ebook’s examples sometimes include import
commands at the beginning, especially where these commands serve
to remind you about which package a newly introduced tool is in.
On the other hand, the import
command is a routinely “necessary
evil” that we need with various tools, as with buffers here. That’s
why we omit some “self-evident” import
s in the ebook text; you’ll
still need to remember to import what you need as you program.
Buffers and import o1._
Actually, we’ve defined our course package o1
in such a way that
if you import its contents, you also get to use buffers without an
additional import
command. You can exploit this to save your
fingers as you use buffers in O1.
import o1._import o1._ Buffer(2.40, 3.11, 4.56, 10.29, 8.11)res3: Buffer[Double] = ArrayBuffer(2.4, 3.11, 4.56, 10.29, 8.11)
Using a Buffer
Each of a buffer’s elements is stored at a particular location within the buffer; each such location is identified by its running number, called an index (indeksi). When we manipulate buffers, we often use indices to target specific elements.
Let’s try examining, modifying, and adding elements. To begin with, here’s an example of a variable that refers to a collection, more specifically a buffer. This variable will be useful for accessing the collection after it’s been created.
val numbers = Buffer(12, 2, 4, 7, 4, 4, 10, 3)numbers: Buffer[Int] = ArrayBuffer(12, 2, 4, 7, 4, 4, 10, 3)
Examining a buffer’s contents
Here’s how you can access the value stored at a particular index. First indicate the buffer that you wish to examine, then put the desired index in round brackets:
numbers(0)res4: Int = 12
Notice that indices start from zero! The above expression accesses the first element of the buffer.
You can use expressions like the one above in many ways. For instance, you can access
a buffer element and assign that value to a variable. In the example below, the buffer’s
fourth element (that is, the value at index three) is assigned to the variable fourthNumber
:
val fourthNumber = numbers(3)fourthNumber: Int = 7
Replacing elements
You can replace an element with a new value by adding an equals sign. In this example, the fourth element in the buffer is replaced by the number one:
numbers(3) = 1
That command does not in itself produce any value of interest. It just instructs the computer to modify the buffer’s contents and doesn’t evaluate to a value in itself. This is why the REPL stays silent. However, we can request the value of the variable and see that the fourth element has indeed changed in the computer’s memory:
numbersres5: Buffer[Int] = ArrayBuffer(12, 2, 4, 1, 4, 4, 10, 3)
This change affected only the buffer, not the variable that we used earlier to store the old value:
fourthNumberres6: Int = 7
Adding an element
The operator +=
adds a new element at the end of the buffer. This means that the
buffer’s size goes up.
numbers += 11res7: Buffer[Int] = ArrayBuffer(12, 2, 4, 1, 4, 4, 10, 3, 11)
Using a buffer is very similar to using a variable, with the addition that we need
indices to target specific elements. In a sense, a buffer is like a numbered list of var
variables. Speaking of which: notice that the variable we used to refer to the buffer
above was a val
. This doesn’t prevent us from modifying the buffer’s contents but it
does prevent us from reassigning to the numbers
variable and making it point to another
buffer.
Empty buffers
It’s often useful to create a buffer that contains no elements yet. For instance, when the GoodStuff application launches, there are initially no recorded experiences even though they may be added later.
The following doesn’t quite accomplish what we want:
val wonderIfThisWorks = Buffer()wonderIfThisWorks: Buffer[Nothing] = ArrayBuffer()
That command did make us a new empty buffer, but empty it will remain. The computer
doesn’t know what we’re planning to store in the buffer, and the Scala toolkit determines
the buffer’s type to be Buffer[Nothing]
. Such a buffer won’t bring us any joy, since we
can’t add anything to it.
We can, however, tell the computer what kind of buffer we want to create. Let’s try
creating an empty buffer that can be subsequently used store strings. The type parameter —
String
in this case — goes in square brackets just like you already saw in REPL outputs:
val words = Buffer[String]()words: Buffer[String] = ArrayBuffer()
We can now add strings to this buffer:
words += "llama"res8: Buffer[String] = ArrayBuffer(llama)
Actually, you’re allowed to type in the type parameter whenever you create a buffer, even where it’s not necessary. This works, too:
val numbers = Buffer[Int](2, -1, 10)numbers: Buffer[Int] = ArrayBuffer(2, -1, 10)
[Int]
because the
Scala toolkit figures out that we want a Buffer[Int]
from the
expressions 2
, -1
, and 10
, which determine the buffer’s
initial elements.Buffer Practice
References
Chapter 1.4 said that a variable stores just a single value. But didn’t we just have a buffer variable that contained multiple values?
Sort of, but not really.
As we take a look at this question, you’ll get to know the concept of reference (viittaus). Eventually, it will turn out that this concept has a significance in programming that extends well beyond buffers.
Did you notice that test
is the name of a variable, not the name of a buffer? (Buffers
don’t have names.) This fact will be important momentarily.
References have a concrete impact on how programs work. Take a look at another animation.
Practice on references
On References and This Chapter
There is a myriad of things that you can do with buffers and other kinds of collections. We have hardly scratched the surface. You’ll see many examples of collections in the chapters that follow; as we already suggested, the experience categories in the GoodStuff application will be one such example. We’ll be storing many different kinds of values in collections, not just individual numbers or strings.
Right now, what matters most is that you know how to create buffers and how to access and modify their individual elements. And that you know that you manipulate buffers via references.
You can expect the concept of reference to come up time and again. We’ll use references to point at many other things than buffers, too.
Beginners often find references a hard concept to grasp. One of the reasons is language.
When we humans talk or write about programming, we often take “shortcuts”. It’s rare for
us to say that “the variable numbers
contains a reference to a buffer”; instead, we
often say “the variable numbers
contains a buffer” or something similarly imprecise.
We may also speak of a “numbers
buffer” even though the variable really contains only
a reference and numbers
is the name of a variable, not of a buffer. (As you’ve seen,
multiple differently named variables can store a reference to the same buffer.) Speaking
simply and imprecisely is natural and convenient, and we don’t need to strictly avoid it,
but if we are to write programs that work, we do need to understand what the words really
mean.
Psst! As a matter of fact...
To be precise, the values of many standard Scala data types —
including String
s — are “at the ends of references” just
like you’ve seen buffers to be. We didn’t bring this up before,
and the animated diagrams in this ebook generally don’t display
references when it comes to certain common data types. But here’s
one animation that is more “truthful” (detailed) than the earlier
animations that featured strings, even if that truthfulness makes
the animation a bit more cumbersome:
Generally, it’s more convenient to think in simplified terms. For example, we may think of the string “cat” itself as being stored in a variable (as in earlier animations) rather than the variable referring to another memory location that contains the string (as above). We’ll continue use the simplified representation in later animations.
The above simplification is “safe” for the String
data type
and won’t lead us to reason erroneously about program behavior
because — unlike a buffer — a string value never changes
internally. Even when we combine a string with another, like in
the preceding example, we don’t modify the existing strings but
create a new string. That’s something we’ll return to in
Chapters 5.2 and 10.2.
Summary of Key Points
- A programmer can store multiple “granules” of information — elements — in a single collection.
- The buffer is one kind of collection.
- Each of a buffer’s elements is associated with a running number called an index.
- You can replace the elements of a buffer with others. You can also add new elements.
- In a sense, a buffer is similar to a bunch of
var
variables grouped together.
- You manipulate buffers via references. A variable can store a
reference that indicates where in the computer’s memory the actual
buffer is.
- Multiple variables may refer to the same buffer.
- After you make a changes to the a buffer’s contents via one variable, you can also observe the change via any of other variables that refer to the same buffer.
- References are similarly used for accessing data other than collections, as you’ll see later.
- Links to the glossary: reference; collection, buffer, element, index; type parameter; package.
An updated concept map:
Feedback
Please note that this section must be completed individually. Even if you worked on this chapter with a pair, each of you should submit the form separately.
Credits
Thousands of students have given feedback that has contributed to this ebook’s design. Thank you!
Weeks 1 to 13 of the ebook, including the assignments and weekly bulletins, have been written in Finnish and translated into English by Juha Sorva.
Weeks 14 to 20 are by Otto Seppälä. That part of the ebook isn’t available during the fall term, but we’ll publish it when it’s time.
The appendices (glossary, Scala reference, FAQ, etc.) are by Juha Sorva unless otherwise specified on the page.
The automatic assessment of the assignments has been developed by: (in alphabetical order) Riku Autio, Nikolas Drosdek, Joonatan Honkamaa, Jaakko Kantojärvi, Niklas Kröger, Teemu Lehtinen, Strasdosky Otewa, Timi Seppälä, Teemu Sirkiä, and Aleksi Vartiainen.
The illustrations at the top of each chapter, and the similar drawings elsewhere in the ebook, are the work of Christina Lassheikki.
The animations that detail the execution Scala programs have been designed by Juha Sorva and Teemu Sirkiä. Teemu Sirkiä and Riku Autio have done the technical implementation, relying on Teemu’s Jsvee and Kelmu toolkits.
The other diagrams and interactive presentations in the ebook are by Juha Sorva.
The O1Library software has been developed by Aleksi Lukkarinen and Juha Sorva. Several of its key components are built upon Aleksi’s SMCL library.
The pedagogy behind O1Library’s tools for simple graphical programming (such as Pic
)
is inspired by the textbooks How to Design Programs by Flatt, Felleisen, Findler, and
Krishnamurthi and Picturing Programs by Stephen Bloch.
The course platform A+ has been created by Aalto’s LeTech research group and is largely developed by students. The current lead developer is Jaakko Kantojärvi; many other students of computer science and information networks are also active on the project.
For O1’s current teaching staff, please see Chapter 1.1.
Additional credits appear at the ends of some chapters.
scala.collection.mutable
.