We will see what is deque data structure and why is this data structure needed and then implement an object-based deque in javascript.
What is deque data structure?
Deque or Double-ended queue is a generalized version of queue in which data can be added and removed from both the ends. It performs both the combined operations of stack and queue together and can be used as any of them.
Why is deque data structure needed?
It supports clockwise and anti-clockwise rotations in O(1) time which can be very useful in certain applications and also the problem where elements need to be removed and added from both the ends can be solved easily.
Example
List of operations performed on Deque
- insertFront(): Inserts an element at the front.
- insertBack(): Inserts an element at the back.
- removeFront(): Removes an element from the front.
- removeBack(): Removes an element from the back.
- getFront(): Returns the element at the front.
- getBack(): Returns the element at the back.
- isEmpty(): Checks if the deque is empty or not.
- size(): Returns the size of the deque.
- clear(): Clears the deque.
- toString(): Returns all the elements concatenated as a string from front to back.
A deque can be implemented either using a doubly-linked list or circular array. In both implementation, we can implement all operations in O(1) time.
Check out how to implement Deque data structure with doubly linked list.
Implementing a deque data structure using array in javascript
Javascript arrays are very flexible and they natively support all the operations of the deque.
Still, we will implement a deque with circular array approach.
We will use two different variables count
and lowestCount
to keep track of the elements on both the end and items
to store the data.
function Deque() { //To track the elements from back let count = 0; //To track the elements from the front let lowestCount = 0; //To store the data let items = {}; //Other methods go here }
Add an item at the front of the deque
We will check if the deque is empty then add the element from the back of the list as it really does not matter, else if there is one item exist then add on the front of it, else shift the existing items and add the current element on the front.
//Add an item on the front this.insertFront = (elm) => { if(this.isEmpty()){ //If empty then add on the back this.insertBack(elm); }else if(lowestCount > 0){ //Else if there is item on the back //then add to its front items[--lowestCount] = elm; }else{ //Else shift the existing items //and add the new to the front for(let i = count; i > 0; i--){ items[i] = items[i - 1]; } count++; items[0] = elm; } }
Add an item at the back of the deque
To add item on the back we will push the new item on the back.
//Add an item on the back of the list this.insertBack = (elm) => { items[count++] = elm; }
Remove an item from the front
To remove the item from the front we just get the item by the lowestCount
tracker and increase it.
//Remove the item from the front this.removeFront = () => { //if empty return null if(this.isEmpty()){ return null; } //Get the first item and return it const result = items[lowestCount]; delete items[lowestCount]; lowestCount++; return result; }
Remove an item from the back
To remove the item from the front we just get the item by decrementing the count
tracker.
//Remove the item from the back this.removeBack = () => { //if empty return null if(this.isEmpty()){ return null; } //Get the last item and return it count--; const result = items[count]; delete items[count]; return result; }
Get the first element from the front
//Peek the first element this.getFront = () => { //If empty then return null if(this.isEmpty()){ return null; } //Return first element return items[lowestCount]; }
Get the first element from the back
//Peek the last element this.getBack = () => { //If empty then return null if(this.isEmpty()){ return null; } //Return first element return items[count - 1]; }
Check if deque is empty
//Check if empty this.isEmpty = () => { return this.size() === 0; }
Get the size of the deque
//Get the size this.size = () => { return count - lowestCount; }
Clear the deque
//Clear the deque this.clear = () => { count = 0; lowestCount = 0; items = {}; }
Get the deque as a string
//Convert to the string //From front to back this.toString = () => { if (this.isEmpty()) { return ''; } let objString = `${items[lowestCount]}`; for (let i = lowestCount + 1; i < count; i++) { objString = `${objString},${items[i]}`; } return objString; }
Complete code of deque data structure
function Deque() { //To track the elements from back let count = 0; //To track the elements from the front let lowestCount = 0; //To store the data let items = {}; //Add an item on the front this.insertFront = (elm) => { if(this.isEmpty()){ //If empty then add on the back this.insertBack(elm); }else if(lowestCount > 0){ //Else if there is item on the back //then add to its front items[--lowestCount] = elm; }else{ //Else shift the existing items //and add the new to the front for(let i = count; i > 0; i--){ items[i] = items[i - 1]; } count++; items[0] = elm; } } //Add an item on the back of the list this.insertBack = (elm) => { items[count++] = elm; } //Remove the item from the front this.removeFront = () => { //if empty return null if(this.isEmpty()){ return null; } //Get the first item and return it const result = items[lowestCount]; delete items[lowestCount]; lowestCount++; return result; } //Remove the item from the back this.removeBack = () => { //if empty return null if(this.isEmpty()){ return null; } //Get the last item and return it count--; const result = items[count]; delete items[count]; return result; } //Peek the first element this.getFront = () => { //If empty then return null if(this.isEmpty()){ return null; } //Return first element return items[lowestCount]; } //Peek the last element this.getBack = () => { //If empty then return null if(this.isEmpty()){ return null; } //Return first element return items[count - 1]; } //Check if empty this.isEmpty = () => { return this.size() === 0; } //Get the size this.size = () => { return count - lowestCount; } //Clear the deque this.clear = () => { count = 0; lowestCount = 0; items = {}; } //Convert to the string //From front to back this.toString = () => { if (this.isEmpty()) { return ''; } let objString = `${items[lowestCount]}`; for (let i = lowestCount + 1; i < count; i++) { objString = `${objString},${items[i]}`; } return objString; } }
Input: let deque = new Deque2(); deque.insertBack(5); deque.insertBack(10); console.log(deque.getBack()); deque.removeBack(); console.log(deque.getBack()); deque.insertFront(15); console.log(deque.getFront()); deque.removeFront(); console.log(deque.getFront()); Output: 10 5 15 5
Making the properties and methods private with closure and IIFE (Immediately Invoked Function Expression).
let Deque = (function(){ return function Deque2() { //To track the elements from back let count = 0; //To track the elements from the front let lowestCount = 0; //To store the data let items = {}; //Add an item on the front this.insertFront = (elm) => { if(this.isEmpty()){ //If empty then add on the back this.insertBack(elm); }else if(lowestCount > 0){ //Else if there is item on the back //then add to its front items[--lowestCount] = elm; }else{ //Else shift the existing items //and add the new to the front for(let i = count; i > 0; i--){ items[i] = items[i - 1]; } count++; items[0] = elm; } } //Add an item on the back of the list this.insertBack = (elm) => { items[count++] = elm; } //Remove the item from the front this.removeFront = () => { //if empty return null if(this.isEmpty()){ return null; } //Get the first item and return it const result = items[lowestCount]; delete items[lowestCount]; lowestCount++; return result; } //Remove the item from the back this.removeBack = () => { //if empty return null if(this.isEmpty()){ return null; } //Get the last item and return it count--; const result = items[count]; delete items[count]; return result; } //Peek the first element this.getFront = () => { //If empty then return null if(this.isEmpty()){ return null; } //Return first element return items[lowestCount]; } //Peek the last element this.getBack = () => { //If empty then return null if(this.isEmpty()){ return null; } //Return first element return items[count - 1]; } //Check if empty this.isEmpty = () => { return this.size() === 0; } //Get the size this.size = () => { return count - lowestCount; } //Clear the deque this.clear = () => { count = 0; lowestCount = 0; items = {}; } //Convert to the string //From front to back this.toString = () => { if (this.isEmpty()) { return ''; } let objString = `${items[lowestCount]}`; for (let i = lowestCount + 1; i < count; i++) { objString = `${objString},${items[i]}`; } return objString; } } }());
Input: let deque = new Deque2(); deque.insertBack(5); deque.insertBack(10); console.log(deque.getBack()); deque.removeBack(); console.log(deque.getBack()); deque.insertFront(15); console.log(deque.getFront()); deque.removeFront(); console.log(deque.getFront()); Output: 10 5 15 5
Class based implementation of deque data structure in javascript
class Deque{ constructor(){ //To track the elements from back this.count = 0; //To track the elements from front this.lowestCount = 0; //To store the elements this.items = {}; } //Add an item on the front insertFront = (elm) => { if(this.isEmpty()){ //If empty then add on the back this.insertBack(elm); }else if(this.lowestCount > 0){ //Else if there is item on the back //then add to its front this.items[--this.lowestCount] = elm; }else{ //Else shift the existing items //and add the new to the front for(let i = this.count; i > 0; i--){ this.items[i] = this.items[i - 1]; } this.count++; this.items[0] = elm; } } //Add an item on the back of the list insertBack = (elm) => { this.items[this.count++] = elm; } //Remove the item from the front removeFront = () => { //if empty return null if(this.isEmpty()){ return null; } //Get the first item and return it const result = this.items[this.lowestCount]; delete this.items[this.lowestCount]; this.lowestCount++; return result; } //Remove the item from the back removeBack = () => { //if empty return null if(this.isEmpty()){ return null; } //Get the last item and return it this.count--; const result = this.items[this.count]; delete this.items[this.count]; return result; } //Peek the first element getFront = () => { //If empty then return null if(this.isEmpty()){ return null; } //Return first element return this.items[this.lowestCount]; } //Peek the last element getBack = () => { //If empty then return null if(this.isEmpty()){ return null; } //Return first element return this.items[this.count - 1]; } //Check if empty isEmpty = () => { return this.size() === 0; } //Get the size size = () => { return this.count - this.lowestCount; } //Clear the deque clear = () => { this.count = 0; this.lowestCount = 0; this.items = {}; } //Convert to the string //From front to back toString = () => { if (this.isEmpty()) { return ''; } let objString = `${this.items[this.lowestCount]}`; for (let i = this.lowestCount + 1; i < this.count; i++) { objString = `${objString},${this.items[i]}`; } return objString; } }
Input: let deque = new Deque2(); deque.insertBack(5); deque.insertBack(10); console.log(deque.getBack()); deque.removeBack(); console.log(deque.getBack()); deque.insertFront(15); console.log(deque.getFront()); deque.removeFront(); console.log(deque.getFront()); Output: 10 5 15 5
Time Complexity
# | Access | Search | Insert | Delete |
---|---|---|---|---|
Average | Θ(1) | Θ(N) | Θ(1) | Θ(1) |
Worst | O(1) | O(N) | O(1) | O(1) |
Space Complexity
# | space |
---|---|
Worst | O(N) |