Start learning free

LESSON 559 OF 573 · OBJECT-ORIENTED PROGRAMMING - CHALLENGES

Ticket Numbers in Go: A Shared Counter Without Static

Practice O(1) Read the full explanation

A ticket machine gives every new ticket the next number: the first ticket gets 1, the next one 2, and so on. The counter belongs to all the tickets together, not to any one of them. Go has no classes and no static: as in the Static Members lesson, the shared counter is a package-level variable, one copy that every function in the package sees, and the "static methods" are plain functions. Each ticket keeps its own number, copied from the counter when the ticket is created.

Write:

  • The Ticket struct, with two fields: name (a string) and number (an int).
  • The package-level counter ticketCount, an int.
  • NewTicket(name): it adds 1 to ticketCount and returns a new Ticket with that name and the new value of ticketCount as its number.
  • issued(): returns how many tickets were created since the counter was last reset.
  • resetCounter(): sets the counter back to 0, so the next ticket gets number 1 again.

For each test case, the test code calls resetCounter(), then creates one ticket per name with NewTicket, in order. Once all the tickets of the case exist, it prints each ticket as its name, a space, # and its number, one ticket per line, and then the value of issued() on a line of its own.

 

Example 1:

Input:
    NewTicket("Ann")
    NewTicket("Bob")
    NewTicket("Cy")
Output:
Ann #1
Bob #2
Cy #3
3
Explanation: resetCounter() set the counter to 0. Each new ticket adds 1 to it and keeps the new value,
so the tickets get 1, 2 and 3, and issued() returns 3.

Example 2:

Input:
    NewTicket("Mia")
    NewTicket("Mia")
Output:
Mia #1
Mia #2
2
Explanation: The number comes from the shared counter, not from the name, so the two tickets for Mia
get different numbers. The numbers start at 1 again because resetCounter() ran before this case.

Example 3:

Input:
    (no tickets)
Output:
0
Explanation: resetCounter() set the counter to 0 and no ticket was created, so issued() returns 0.

 

Constraints:

  • A test case creates from 0 to 1000 tickets.
  • A name is one word of 1 to 20 English letters. Two tickets can have the same name.
  • The tickets are printed only after all of them were created, so every ticket must store its own number: reading ticketCount when printing would give every ticket the last number.
STEP 1 OF 6
    Ctrl + Enter to check

    GO · OBJECT-ORIENTED PROGRAMMING - CHALLENGES

    Ticket Numbers in Go, explained

    Problem overview

    A ticket machine gives every new ticket the next number: 1 for the first, 2 for the next, and so on. Each ticket keeps its own number, but the counter that hands the numbers out belongs to all tickets together, not to any one of them.

    In languages with classes, a counter like that is a static field, and the functions that read or reset it are static methods. Go has neither classes nor static. The same design uses a package-level variable, declared outside any function, so there is exactly one copy and every function in the package sees it. The "static methods" become plain functions.

    The lesson asks for five things: the Ticket struct with a name and a number, the counter ticketCount, NewTicket(name), issued() and resetCounter().

    Approach

    There are two kinds of data here, and they live in different places:

    • Per ticket: the name and the number. Every Ticket value has its own copy.
    • Shared: how many tickets were made. One variable for the whole package.

    Making a ticket is two actions in a fixed order: first add 1 to the counter, then build the ticket with the counter's new value as its number. The first ticket after a reset finds the counter at 1, so numbers start at 1.

    The key point is that the ticket copies the value. The struct literal reads ticketCount at that moment and stores the result in the ticket's number. When the counter moves on, the old tickets keep their numbers. This matters because the tests create all the tickets first and print them afterwards: a ticket that read the counter when printed would show the last number, not its own.

    Every function is constant time.

    Step by step

    1. Declare the fields of Ticket: name as a string and number as an int.
    2. Declare the counter below the struct and outside any function: var ticketCount int. Package-level variables start at the zero value, so it starts at 0.
    3. In NewTicket, add 1 to ticketCount with ++. Every ticket counts once, whatever its name.
    4. Return the new ticket, with name as its name and ticketCount as its number.
    5. Write issued, which returns ticketCount. It is a plain function: it needs no ticket, as a static method needs no object.
    6. Write resetCounter, which assigns 0 to ticketCount. The next ticket will then get 1 again.

    The solution in Go

    type Ticket struct {
        name string
        number int
    }
    var ticketCount int
    func NewTicket(name string) Ticket {
        ticketCount++
        return Ticket{name: name, number: ticketCount}
    }
    func issued() int {
        return ticketCount
    }
    func resetCounter() {
        ticketCount = 0
    }
    

    Go details:

    • Package scope. A variable declared outside every function belongs to the package. Functions can read and change it without receiving it as a parameter.
    • ++ is a statement. In Go, ticketCount++ is a statement on its own line, not an expression. You can't write number: ticketCount++, which is why the increment comes on its own line before the return.
    • Value copy. int is a plain value. number: ticketCount copies it, and later changes to ticketCount do not reach the ticket.
    • Shared state is shared by everything. Because the counter is one variable, a reset affects every future ticket. Use this kind of variable sparingly: it is global state, and in a program that creates tickets from several goroutines at once it would need protection, which this lesson doesn't cover.

    Using it

    resetCounter()
    tickets := []Ticket{NewTicket("Ann"), NewTicket("Bob"), NewTicket("Cy")}
    for _, t := range tickets {
        fmt.Printf("%s #%d\n", t.name, t.number)
    }
    fmt.Println(issued())
    resetCounter()
    m1 := NewTicket("Mia")
    m2 := NewTicket("Mia")
    fmt.Println(m1.number, m2.number, issued())
    resetCounter()
    fmt.Println(issued())
    

    This prints:

    Ann #1
    Bob #2
    Cy #3
    3
    1 2 2
    0
    

    The tickets for Mia get different numbers because the number comes from the counter, not from the name. After the final reset, with no tickets made, issued() is 0.

    Edge cases and common mistakes

    • Reading the counter before adding 1. Building the ticket first and incrementing later gives the first ticket number 0, and every number is one too low.
    • Storing the counter in each ticket. A Ticket field named count would give every ticket its own counter, and each ticket would number itself 1.
    • Reading ticketCount when printing. Every ticket would show the last number. The number has to be copied into the ticket when it is created.
    • Forgetting resetCounter. Without it, a second group of tickets would carry on from where the first stopped instead of starting at 1.
    • Shadowing the counter. Writing ticketCount := 0 inside resetCounter declares a new local variable and leaves the shared one alone. Use =, not :=.
    Keep going with Go500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free