Problem overview
A ticket machine gives every new ticket the next number: 1 for the first ticket, 2 for the next, and so on. The counter belongs to the class, not to any one ticket, so it is a static member of Ticket: one copy shared by all tickets. Each ticket keeps its own name and its own number, copied from the counter when the ticket is created.
The class has three parts:
Ticket(name)adds 1 to the counter and gives the new ticket that value as its number;- the static method
issued()returns how many tickets were created since the counter was last reset; - the static method
resetCounter()sets the counter back to 0, so the next ticket gets number 1 again.
Creating tickets for Ann, Bob and Cy gives them numbers 1, 2 and 3, and issued() returns 3. The lesson's tests print each ticket as its name, a space, # and its number, then the value of issued() on its own line.
Approach
There are two kinds of data here, and they live in different places:
- the counter is one value for the whole class, so it is declared
staticand is reached through the type name,Ticket.count; - the name and number differ from ticket to ticket, so they are ordinary properties, one pair per object.
The important detail is the order inside init: first move the counter forward, then copy its value into the ticket. The copy stays put when the counter moves on, which is why every ticket must store its own number. Everything is O(1).
Step by step
- Declare the counter:
static var count = 0at the top of the class. Thestatickeyword makes it belong to the type. - Declare the per-ticket properties:
let name: Stringandlet number: Int, withoutstatic. They areletbecause a ticket never changes once it exists. - Store the name in
init:self.name = name. - Add 1 to the shared counter:
Ticket.count += 1. Every ticket counts once, whatever its name. - Copy the counter into the ticket:
self.number = Ticket.count. Right after a reset the first ticket finds the counter at 1, so numbering starts at 1. issued()is astatic functhat returnsTicket.count. The tests call it on the type, with no ticket at all.resetCounter()is astatic functhat assigns 0 toTicket.count.
The solution in Swift
class Ticket {
static var count = 0
let name: String
let number: Int
init(_ name: String) {
self.name = name
Ticket.count += 1
self.number = Ticket.count
}
static func issued() -> Int {
return Ticket.count
}
static func resetCounter() {
Ticket.count = 0
}
}
Using it:
Ticket.resetCounter()
var tickets: [Ticket] = []
for n in ["Ann", "Bob", "Cy"] { tickets.append(Ticket(n)) }
for t in tickets { print("\(t.name) #\(t.number)") }
print(Ticket.issued())
Ticket.resetCounter()
print(Ticket.issued())
let m = Ticket("Mia")
print(m.name, m.number)
Ann #1
Bob #2
Cy #3
3
0
Mia 1
After the reset, issued() is 0 and the next ticket is number 1 again.
Swift details worth knowing
- Static members are reached through the type name. Inside
inityou writeTicket.count, not plaincount, and the tests callTicket.issued(). An object has noissuedof its own. init(_ name: String)has an underscore, so a call isTicket("Ann")without a label.- A
letproperty must be set exactly once ininit.self.nameandself.numbereach get one assignment, and nothing can change them afterwards. - Two names for two kinds of data. The static counter is called
countand the per-ticket valuenumber, so reading the code never mixes them up.
Edge cases and common mistakes
- Reading the counter later instead of storing the number. If the tests print
Ticket.countfor each ticket after all were created, every line shows the last number. With the three tickets above it printsAnn #3,Bob #3,Cy #3. Storingnumberin each ticket avoids this. - No tickets at all. After
resetCounter()and no new ticket,issued()returns 0. - Equal names. Two tickets named Mia get different numbers, because the number comes from the counter and not from the name.
- Copying before incrementing. If
self.number = Ticket.countcomes beforeTicket.count += 1, the numbers start at 0: Ann, Bob and Cy get 0, 1 and 2 whileissued()returns 3. - Forgetting the reset. The counter lives as long as the program, so without
resetCounter()a second group of tickets would continue from where the first one stopped.
Complexity
Creating a ticket, issued() and resetCounter() each do a constant number of steps, so each is O(1) time. The class stores one shared integer plus a name and a number per ticket, so a test with n tickets uses O(n) space, which is just the tickets themselves.
Static or per-object?
A quick rule decides where each piece of data goes. Ask: "if I make a thousand objects, should there be a thousand copies of this, or one?" The name and the number differ per ticket, so there are a thousand copies. The count of tickets made so far is the same fact for everybody, so there is one.
- Three tickets, Ann, Bob and Cy, after a reset: numbers 1, 2, 3 and
issued()is 3. - Two tickets both named Mia: numbers 1 and 2,
issued()is 2. - No tickets after a reset:
issued()is 0.
