Exercises for Lesson 10
Exercise 1: Implementing a queue
Consider the following start of a queue implementation in Kotlin:
interface Queue<T> {
fun isEmpty(): Boolean
fun enqueue(item: T)
fun dequeue(): T
}
class ListQueue<T> : Queue<T> {
// The underlying data type we'll use is a mutable list
private val items: MutableList<T> = mutableListOf()
// We need to enqueue on the end and dequeue from the front,
// so we'll track both head and tail indices
private var head: Int = -1 // -1 means empty
private var tail: Int = -1 // -1 means empty
override fun isEmpty(): Boolean {
return head == -1 // could just as easily use the tail
}
override fun enqueue(item: T) {
// If the queue is empty, we have a valid head index now
if (isEmpty()) {
head = 0
}
// Increment the tail index, then add the item
// at the new tail position
tail++
if (items.count() > tail) { // Question: Why do this check?
items[tail] = item
} else {
items.add(item)
}
}
override fun toString(): String {
return items.subList(head, tail+1).toString()
}
}Time complexity
What is the time complexity of enqueue, assuming there are already n elements in the queue?
Removing from a queue
How would you implement the dequeue method?
Here is a main function to help you test it out:
fun main() {
val q = ListQueue<Int>()
q.enqueue(1)
q.enqueue(2)
q.enqueue(3)
println(q)
q.dequeue()
q.enqueue(6)
q.dequeue()
println(q)
}If you finish early, think about: how you would implement this as using a circular array…