Last time I looked at implementing the Stack Data Structure in Python and C#. In this article I'll be implementing similar solutions in Python and C# using the Queue Data Structure. The Stack is a last-in, first-out data structure and the Queue is a first-in, first-out data structure.

Deque in Python

The collections module in Python has a lot of feature-rich containers, and one of them is deque. It allows for fast appends and pops to both the left and right sides of the container, making it a great solution for both Stacks and Queues in Python. Here is an example in Python using deque as a Queue.

from collections import deque


queue = deque()

queue.append(1)
queue.append(2)
queue.append(3)

is_empty = len(queue) == 0
print(is_empty)  # False

print(queue.popleft())  # 1
print(queue.popleft())  # 2
print(queue.popleft())  # 3


is_empty = len(queue) == 0
print(is_empty)  # True

print(queue.popleft())  # IndexError: pop from an empty deque

Using Link List for Queue in Python

One can use a Link List as a Queue in Python. In order to make appends really fast I will add a tail pointer in addition to the normal head pointer. I can pop from the head of the Link List in O(1) with the head pointer, and append to the tail of the Link List in O(1) with the tail pointer.

class Node:
    def __init__(self, key):
        self.key = key
        self.next = None


class Queue:
    def __init__(self):
        self.head = None
        self.tail = None

    def append(self, key):
        new_node = Node(key)

        # empty. add first node.
        if self.is_empty():
            self.head = new_node
            self.tail = self.head
            return

        # not empty. add node to the tail.
        self.tail.next = new_node
        self.tail = new_node

    def pop(self):
        if self.is_empty():
            raise IndexError('pop from an empty queue')

        key = self.head.key

        # if only 1 item in queue, otherwise...
        if self.head is self.tail:
            self.head = None
            self.tail = self.head
        else:
            self.head = self.head.next

        return key

    def is_empty(self):
        return self.head is None


queue = Queue()

queue.append(1)
queue.append(2)
queue.append(3)

print(queue.is_empty())  # False

print(queue.pop())  # 1
print(queue.pop())  # 2
print(queue.pop())  # 3

print(queue.is_empty())  # True

print(queue.pop())  # IndexError: pop from an empty queue

Queue Class as Abstraction in Python

The custom Queue Class used with the Link List abstracts away the details of using a Link List to handle Queue operations. In fact, it is easy to change the underlying implementation of using a Link List with deque and preserve the operations of pop, append, and is_empty. Let's do that to show that abstraction is a very useful concept in computer science. Here is the same Queue class as above using deque in place of a Link List.

from collections import deque


class Queue:
    def __init__(self):
        self.__queue = deque()

    def append(self, key):
        self.__queue.append(key)

    def pop(self):
        return self.__queue.popleft()

    def is_empty(self):
        return len(self.__queue) == 0


queue = Queue()

queue.append(1)
queue.append(2)
queue.append(3)

print(queue.is_empty())  # False

print(queue.pop())  # 1
print(queue.pop())  # 2
print(queue.pop())  # 3

print(queue.is_empty())  # True

print(queue.pop())  # IndexError: pop from an empty dequeue

Using Array as Queue in C#

One can also use an Array as a Queue, but there is a fixed limit to the number of items that can be queued using an Array. I am talking about a fixed array and not a Dynamic Array.

Python does not have Arrays, so let's explore this concept in C#. Using an Array as a Queue will require a read index and a write index. The read index will point to the index in the Array for the next read. The write index will point to the index in the Array where the next value will be written. The Array can be thought of as a circular Queue, such that when the read and write index hit the end of the Array they can loop back to the beginning of the Array. When the read index is equal to the write index the Queue is empty. Note that we need an empty cell between the read index and the write index in order to differentiate between an empty Queue and a full Queue. Therefore when I initialize the Array, I tack on one extra cell for the differentiation.

Here is a rough example of using an Array as a Queue in C# to help one understand the concept.

public class Queue<T> {
    private readonly T[] _queue;
    private int _readIndex;
    private int _writeIndex;

    public Queue(int length = 20) {
        // Need empty cell to differentiate
        // between full and empty.
        _queue = new T[length + 1];
    }

    public void Append(T key) {
        if (IsFull())
            throw new IndexOutOfRangeException("append to full queue.");

        _queue[_writeIndex] = key;
        _writeIndex = (_writeIndex + 1)%_queue.Length;
    }

    public T Pop() {
        if (IsEmpty())
            throw new IndexOutOfRangeException("pop from empty queue.");

        var key = _queue[_readIndex];
        _readIndex = (_readIndex + 1)%_queue.Length;

        return key;
    }

    public bool IsEmpty() {
        return _readIndex == _writeIndex;
    }

    public bool IsFull() {
        return (_writeIndex + 1)%_queue.Length == _readIndex;
    }
}

Here is a C# Script similar to the Python Scripts that show the Queue's API. Since the Queue can be full in this case, I added an IsFull Method.

var queue = new Queue<int>(3);

queue.Append(1);
queue.Append(2);
queue.Append(3);

Console.WriteLine(queue.IsEmpty()); // False
Console.WriteLine(queue.IsFull()); // True

Console.WriteLine(queue.Pop()); // 1
Console.WriteLine(queue.Pop()); // 2
Console.WriteLine(queue.Pop()); // 3

Console.WriteLine(queue.IsEmpty()); // True
Console.WriteLine(queue.IsFull()); // False

Console.WriteLine(queue.Pop()); // IndexError: pop from an empty queue

Queue Class in C# from System.Collections

There is a Queue Class in System.Collections and Queue<T> Class in System.Collections.Generic that provides a Queue Data Structure for your .NET Applications. Rather than using append and pop, it uses enqueue and dequeue to represent appending and removing items from the Queue.

var queue = new Queue<int>(3);

queue.Enqueue(1);
queue.Enqueue(2);
queue.Enqueue(3);

Console.WriteLine(queue.Count == 0); // False

Console.WriteLine(queue.Dequeue()); // 1
Console.WriteLine(queue.Dequeue()); // 2
Console.WriteLine(queue.Dequeue()); // 3

Console.WriteLine(queue.Count == 0); // True

Console.WriteLine(queue.Dequeue()); // IndexOperationException

Conclusion

Hopefully this helps explain different ways of implementing a Queue in Python and C#. There is no need to implement a Queue yourself using an Array or Link List. I just added these to help explain the concepts of how a Queue could be implemented under the covers. The deque container in the collections module in Python and the Queue and Queue<T> Classes in System.Collections and System.Collections.Generic in C# are highly optimized solutions for implementing a Queue in your applications.

Best Wishes!

Posted by Koder Dojo

Hi, and welcome to Koder Dojo! I am a freelance, C#, ASP.NET MVC Developer learning Python and various topics on computer science, algorithms, data structures, and data science. I am sharing my daily adventures to help and inspire others as well as reinforce this new knowledge to memory. Many of these topics are from online computer science classes that I am attending, books I am reading, or a part of programming challenges. I hope you find them useful. You can find me on Twitter as @KoderDojo.

Related Posts: