Priority Queue Data Structure
August 16, 2022Introduction
Yesterday's article was about the Queue data structure. Today we will be looking at the Priority Queue data structure.
A priority Queue is very similar to a Queue. The difference is that the elements in the Priority Queue are ordered by priority.
To create a Queue, we will not only use the knowledge from the previous article but also the Queue factory function.
Priority Queue Operations
Priority Queue has the same operations as a Queue. The only difference is that the enqueue
operation will accept a priority as a parameter.
Implementation
TypeScript definition
The TypeScript definition is almost the same as for Queue, The only difference is that enqueue
operation will accept a priority (high
or low
) as a argument.
1type CreatePriorityQueueReturnType<T> = {2 enqueue: (item: T, priority?: 'high' | 'low') => void3 dequeue: () => void4 isEmpty: () => boolean5 length: number6 peek: () => T7}
Factory function implementation
1// `createPriorityQueue` factory function accepts generic `T` type variable2// `T` type variable will be used within `CreatePriorityQueueReturnType` and inside of factory function3// `T` type variable will be also passed down to the `createQueue` factory function4export function createPriorityQueue<T>(): CreatePriorityQueueReturnType<T> {5 // We need to create at least two queues: one for high priority and one for low priority items6 const highPriorityQueue = createQueue<T>()7 const lowPriorityQueue = createQueue<T>()89 return {10 enqueue() {},11 dequeue() {},12 isEmpty() {},13 // we need to use `get` to access the latest `length` property of the queue14 get length() {},15 peek() {},16 }17}
Adding tests
As we have the createPriorityQueue
function specified, as well as return types of factory function, we can write a test suite before implementing methods of that function.
1import { createPriorityQueue } from './priority-queue'23describe('priorityQueue', () => {4 const priorityQueue = createPriorityQueue<string>()56 it('should be empty when created', () => {7 expect(priorityQueue.isEmpty()).toBe(true)8 expect(priorityQueue.length).toBe(0)9 })1011 it('should enqueue and dequeue in the priority order', () => {12 // `priority` argument is optional, default value is 'low'13 // 'First low priority item' will be added to the low priority queue14 priorityQueue.enqueue('First low priority item', 'low')15 // 'Second low priority item' will be added to the low priority queue16 priorityQueue.enqueue('Second low priority item')1718 expect(priorityQueue.peek()).toBe('First low priority item')19 expect(priorityQueue.length).toBe(2)2021 // 'First high priority item' will be added to the high priority queue22 priorityQueue.enqueue('First high priority item', 'high')23 // 'Second high priority item' will be added to the high priority queue24 priorityQueue.enqueue('Second high priority item', 'high')2526 expect(priorityQueue.peek()).toBe('First high priority item')27 expect(priorityQueue.length).toBe(4)2829 // dequeue (remove) the 'First high priority item' from the high priority queue30 priorityQueue.dequeue()3132 expect(priorityQueue.peek()).toBe('Second high priority item')33 expect(priorityQueue.length).toBe(3)3435 // dequeue (remove) the 'Second high priority item' from the high priority queue36 priorityQueue.dequeue()3738 expect(priorityQueue.peek()).toBe('First low priority item')39 expect(priorityQueue.length).toBe(2)4041 // dequeue (remove) the 'First low priority item' from the low priority queue42 priorityQueue.dequeue()4344 expect(priorityQueue.peek()).toBe('Second low priority item')45 expect(priorityQueue.length).toBe(1)4647 // dequeue (remove) the 'Second low priority item' from the low priority queue48 priorityQueue.dequeue()49 expect(priorityQueue.isEmpty()).toBe(true)50 expect(priorityQueue.length).toBe(0)51 })52})
Implement enqueue
1export function createPriorityQueue<T>(): CreatePriorityQueueReturnType<T> {2 const highPriorityQueue = createQueue<T>()3 const lowPriorityQueue = createQueue<T>()45 return {6 // `priority` argument is optional, default value is 'low'7 enqueue(item, priority = 'low') {8 // check the priority and add the item to the appropriate queue9 if (priority === 'high') {10 highPriorityQueue.enqueue(item)11 } else {12 lowPriorityQueue.enqueue(item)13 }14 },15 dequeue() {},16 isEmpty() {},17 get length() {},18 peek() {},19 }20}
Implement dequeue
1export function createPriorityQueue<T>(): CreatePriorityQueueReturnType<T> {2 const highPriorityQueue = createQueue<T>()3 const lowPriorityQueue = createQueue<T>()45 return {6 // `priority` argument is optional, default value is 'low'7 enqueue(item, priority = 'low') {8 // check the priority and add the item to the appropriate queue9 if (priority === 'high') {10 highPriorityQueue.enqueue(item)11 } else {12 lowPriorityQueue.enqueue(item)13 }14 },15 dequeue() {16 // dequeue priority queue first, then dequeue low priority queue17 if (!highPriorityQueue.isEmpty()) {18 highPriorityQueue.dequeue()19 } else {20 lowPriorityQueue.dequeue()21 }22 },23 isEmpty() {},24 get length() {},25 peek() {},26 }27}
Implement isEmpty
1export function createPriorityQueue<T>(): CreatePriorityQueueReturnType<T> {2 const highPriorityQueue = createQueue<T>()3 const lowPriorityQueue = createQueue<T>()45 return {6 // `priority` argument is optional, default value is 'low'7 enqueue(item, priority = 'low') {8 // check the priority and add the item to the appropriate queue9 if (priority === 'high') {10 highPriorityQueue.enqueue(item)11 } else {12 lowPriorityQueue.enqueue(item)13 }14 },15 dequeue() {16 // dequeue priority queue first, then dequeue low priority queue17 if (!highPriorityQueue.isEmpty()) {18 highPriorityQueue.dequeue()19 } else {20 lowPriorityQueue.dequeue()21 }22 },23 isEmpty() {24 // check if both queues are empty25 return highPriorityQueue.isEmpty() && lowPriorityQueue.isEmpty()26 },27 get length() {},28 peek() {},29 }30}
Implement length getter
1export function createPriorityQueue<T>(): CreatePriorityQueueReturnType<T> {2 const highPriorityQueue = createQueue<T>()3 const lowPriorityQueue = createQueue<T>()45 return {6 // `priority` argument is optional, default value is 'low'7 enqueue(item, priority = 'low') {8 // check the priority and add the item to the appropriate queue9 if (priority === 'high') {10 highPriorityQueue.enqueue(item)11 } else {12 lowPriorityQueue.enqueue(item)13 }14 },15 dequeue() {16 // dequeue priority queue first, then dequeue low priority queue17 if (!highPriorityQueue.isEmpty()) {18 highPriorityQueue.dequeue()19 } else {20 lowPriorityQueue.dequeue()21 }22 },23 isEmpty() {24 // check if both queues are empty25 return highPriorityQueue.isEmpty() && lowPriorityQueue.isEmpty()26 },27 get length() {28 // return the sum of the length of the high priority queue and the low priority queue29 return highPriorityQueue.length + lowPriorityQueue.length30 },31 peek() {},32 }33}
Implement peek
1export function createPriorityQueue<T>(): CreatePriorityQueueReturnType<T> {2 const highPriorityQueue = createQueue<T>()3 const lowPriorityQueue = createQueue<T>()45 return {6 // `priority` argument is optional, default value is 'low'7 enqueue(item, priority = 'low') {8 // check the priority and add the item to the appropriate queue9 if (priority === 'high') {10 highPriorityQueue.enqueue(item)11 } else {12 lowPriorityQueue.enqueue(item)13 }14 },15 dequeue() {16 // dequeue priority queue first, then dequeue low priority queue17 if (!highPriorityQueue.isEmpty()) {18 highPriorityQueue.dequeue()19 } else {20 lowPriorityQueue.dequeue()21 }22 },23 isEmpty() {24 // check if both queues are empty25 return highPriorityQueue.isEmpty() && lowPriorityQueue.isEmpty()26 },27 get length() {28 // return the sum of the length of the high priority queue and the low priority queue29 return highPriorityQueue.length + lowPriorityQueue.length30 },31 peek() {32 // if the high priority queue is not empty, return the peek of the high priority queue33 // otherwise return the peek of the low priority queue34 if (!highPriorityQueue.isEmpty()) {35 return highPriorityQueue.peek()36 } else {37 return lowPriorityQueue.peek()38 }39 },40 }41}
Complete Implementation
1import { createQueue } from './queue'23type CreatePriorityQueueReturnType<T> = {4 dequeue: () => void5 enqueue: (item: T, priority?: 'high' | 'low') => void6 isEmpty: () => boolean7 length: number8 peek: () => T9}1011export function createPriorityQueue<T>(): CreatePriorityQueueReturnType<T> {12 const highPriorityQueue = createQueue<T>()13 const lowPriorityQueue = createQueue<T>()1415 return {16 dequeue() {17 if (!highPriorityQueue.isEmpty()) {18 highPriorityQueue.dequeue()19 } else {20 lowPriorityQueue.dequeue()21 }22 },23 enqueue(item, priority = 'low') {24 if (priority === 'high') {25 highPriorityQueue.enqueue(item)26 } else {27 lowPriorityQueue.enqueue(item)28 }29 },30 isEmpty() {31 return highPriorityQueue.isEmpty() && lowPriorityQueue.isEmpty()32 },33 get length() {34 return highPriorityQueue.length + lowPriorityQueue.length35 },36 peek() {37 if (!highPriorityQueue.isEmpty()) {38 return highPriorityQueue.peek()39 } else {40 return lowPriorityQueue.peek()41 }42 },43 }44}
Conclusion
In today's article, we've implemented the priority queue data structure. Our implementation is both type-safe and covered by tests, so we can build additional methods on top of our priority queue.
Have you found any bugs, or do you have any questions? Feel free to reach out on Twitter.
If you do not want to miss the next article, click on the follow button!