Problem overview
A ticket machine gives each new ticket the next number: 1 for the first, 2 for the second, and so on. The counter belongs to the class as a whole, not to any one ticket, so all tickets share one copy. Each ticket keeps its own number, copied from the counter when the ticket is created.
The class Ticket needs:
Ticket(name), which adds 1 to the shared counter and keeps the new value as the ticket'snumber, next to itsname;issued(), which returns how many tickets were created since the last reset;resetCounter(), which sets the counter back to 0, so the next ticket is number 1 again.
Creating tickets for Ann, Bob and Cy gives them numbers 1, 2 and 3, and issued() returns 3.
Approach: the counter lives in the companion object
Scala has no static keyword. Instead, a class can have a companion object: an object with the same name, in the same file. It exists once for the whole program, so it is the place for anything that is shared, such as the counter and the two methods that read and reset it. Code anywhere can call them on the name: Ticket.issued(), with no ticket needed.
The ticket's own number must be a field of the ticket, copied from the counter at creation. If a ticket read the counter every time it was asked for its number, every ticket would show the latest number once more tickets existed.
Step by step
- Declare the counter in the companion object:
var count = 0. It is avarbecause it changes, and there is one copy for all tickets. - Count the new ticket. The statements in a class body run every time an object is created, so
Ticket.count += 1there runs once per ticket. - Keep the number.
val number: Int = Ticket.countcopies the counter's current value into this ticket. The counter moves on as more tickets are made, but the copy stays. Right after a reset, the first ticket sees 1 here. issuedreturnscount.resetCounterassigns 0 tocount.
The solution in Scala
class Ticket(val name: String) {
Ticket.count += 1
val number: Int = Ticket.count
}
object Ticket {
var count = 0
def issued(): Int = {
return count
}
def resetCounter(): Unit = {
count = 0
}
}
Scala details:
- The class and its companion object share a name and must be in the same file. The class reaches the counter as
Ticket.count, and the object's methods usecountdirectly. - The class body is the constructor. Besides declaring fields, it runs its statements for each new object. That is what makes
Ticket.count += 1happen once per ticket. - The order matters. The increment comes before
val number, so the number is the already increased value. namecomes from the header.val name: Stringin the parentheses makes it a field, sot.nameworks.
Using the class
Ticket.resetCounter()
val ts = List(new Ticket("Ann"), new Ticket("Bob"), new Ticket("Cy"))
for (t <- ts) println(s"${t.name} #${t.number}")
println(Ticket.issued())
It prints:
Ann #1
Bob #2
Cy #3
3
All three tickets exist before anything is printed, yet each one shows its own number. After another Ticket.resetCounter(), Ticket.issued() returns 0. Two tickets for the same name get different numbers: with new Ticket("Mia") twice after a reset, the numbers are 1 and 2 and issued() is 2, because the number comes from the counter, not from the name.
Complexity
Creating a ticket, issued() and resetCounter() each take O(1) time. Each ticket stores a name and a number.
Common mistakes
- Making the number a method that reads the counter. Then every ticket reports the latest value. Copy it into a field at creation.
- Putting the counter in the class. A
varinsideclass Ticketis a separate copy for every ticket, so each would count only itself and every number would be 1. - Taking the number before the increment. The first ticket would get 0.
- Forgetting the reset. The counter would carry on from an earlier set of tickets instead of starting at 1.
