Start learning free

LESSON 559 OF 573 · OBJECT-ORIENTED PROGRAMMING - CHALLENGES

Ticket Numbers in Kotlin: Companion Object Counter

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 the class, not to any one ticket: it is a static member of Ticket, one copy shared by all the tickets. Each ticket keeps its own number, copied from the counter when the ticket is created.

Implement the class Ticket:

  • Ticket(name) creates a ticket for name. It adds 1 to the class-level counter and gives the new ticket that value as its number. The ticket keeps its name and its number in two fields, name and 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.

For each test case, the test code calls resetCounter() through the class name, then creates one ticket per name, 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.

The starter code spells these names the way your language does: reset_counter in Python, Issued and ResetCounter in C#. In Python the counter is a class attribute and the two methods are marked @staticmethod; in Kotlin and Scala the counter and the two methods live in the companion object, as in the Static Members lesson.

 

Example 1:

Input:
    Ticket("Ann")
    Ticket("Bob")
    Ticket("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:
    Ticket("Mia")
    Ticket("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 the shared counter when printing would give every ticket the last number.
STEP 1 OF 6
    Ctrl + Enter to check

    KOTLIN · OBJECT-ORIENTED PROGRAMMING - CHALLENGES

    Ticket Numbers in Kotlin, explained

    Problem overview

    A ticket machine gives every new ticket the next number: the first ticket is 1, the next is 2, and so on. The counter belongs to the class Ticket, not to any one ticket, so there is one copy shared by all tickets. Each ticket also keeps its own number, copied from the counter at the moment the ticket is created.

    You write the class Ticket:

    • Ticket(name) adds 1 to the shared counter and stores that value as the ticket's number, next to its name.
    • Ticket.issued() returns how many tickets were created since the last reset.
    • Ticket.resetCounter() sets the counter back to 0, so the next ticket is number 1 again.

    Create tickets for Ann, Bob and Cy after a reset, and they get 1, 2 and 3, and issued() returns 3. If no ticket is created, issued() returns 0.

    Approach: shared state versus per-object state

    Two kinds of data are at work, and the whole lesson is about telling them apart.

    • Per-object data is different for every ticket: the name and the number. Each object has its own copy.
    • Per-class data is the counter. There is exactly one, and every ticket sees the same one.

    Kotlin has no static keyword. Whatever you put in a class's companion object exists once for the whole class, and you reach it through the class name: Ticket.issued(), with no ticket needed. Inside the class, the companion's members can be used without the Ticket. prefix.

    The important trap is the number. The counter keeps moving as more tickets are made, so a ticket must copy the counter's value when it is created. If a ticket only looked at the counter later, every ticket would show the last number.

    Step by step

    1. The counter. Inside the companion object, declare var count = 0. It is a var because it changes.
    2. The ticket's own number. In the class body, declare val number: Int with no value. A val without an initial value must be assigned exactly once before the object is finished, which is what an init block can do. After that it never changes, as a ticket number shouldn't.
    3. Count the new ticket. In init, run count++. Every ticket counts once, whatever its name.
    4. Keep a copy. Then write number = count. Right after a reset, the first ticket finds the counter at 1 here, so numbering starts at 1.
    5. issued returns count.
    6. resetCounter assigns 0 to count.

    The order inside init matters: increment first, then copy. Copying first would give the first ticket number 0.

    The solution in Kotlin

    class Ticket(val name: String) {
        companion object {
            var count = 0
    
            fun issued(): Int {
                return count
            }
    
            fun resetCounter() {
                count = 0
            }
        }
    
        val number: Int
    
        init {
            count++
            number = count
        }
    }
    

    Some Kotlin details here:

    • val name in the header makes name a property of each ticket, set from the constructor argument.
    • The init block runs once for each object, when it is created, after the properties above it are set up. That is why it can change the shared count and set this ticket's number.
    • The members of the companion object are called on the class: Ticket.issued() and Ticket.resetCounter(). A ticket object has no issued method of its own.

    Using the class

    fun main() {
        Ticket.resetCounter()
        val t1 = Ticket("Ann")
        val t2 = Ticket("Bob")
        val t3 = Ticket("Cy")
        println("${t1.name} #${t1.number}")
        println("${t2.name} #${t2.number}")
        println("${t3.name} #${t3.number}")
        println(Ticket.issued())
    
        Ticket.resetCounter()
        println(Ticket.issued())
        val t4 = Ticket("Mia")
        println("${t4.name} #${t4.number} ${Ticket.issued()}")
    }
    

    It prints:

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

    All three tickets are printed after they were all created, and each still shows its own number. The earlier tickets keep 1, 2 and 3 even after resetCounter(): a reset changes the counter only, not tickets that exist already.

    What lives where

    It helps to picture the memory. The class has one box, count. Each ticket has two boxes, name and number. Creating a ticket does three things in order:

    1. the new object gets its empty boxes and its name from the constructor argument;
    2. the init block adds 1 to the one shared box;
    3. the block copies the shared box's value into the ticket's own number box.

    After step 3 the ticket no longer depends on the counter. That is why tickets created earlier keep their numbers while the counter moves on, and why printing all the tickets at the end, after the last was created, still shows 1, 2, 3 and not 3, 3, 3.

    Two tickets can have the same name, as in two tickets for Mia: the number comes from the counter, never from the name, so they get different numbers.

    Testing it

    • No tickets after a reset: issued() returns 0.
    • One ticket after a reset: its number is 1 and issued() returns 1.
    • Two batches with a reset between them: the second batch starts at 1 again.
    • Many tickets, such as 1000: the last number equals issued().

    Common mistakes

    • Making the number a function of the counter. A property like fun number() = count would return the current counter, so every ticket would report the last value.
    • Putting the counter in the class body instead of the companion. A plain var count = 0 there belongs to each ticket, so every ticket starts its own count at 0 and gets number 1.
    • Copying before incrementing. number = count then count++ numbers the first ticket 0, and issued() stays one ahead of the numbers.
    • Forgetting the reset. The counter lives as long as the program, so a second batch of tickets continues from where the first one stopped unless resetCounter() runs first.
    • Reassigning number. It is a val, so the compiler refuses a second assignment.
    Keep going with Kotlin500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free