Skip to content

Commit 6bb32ed

Browse files
shahidansari311trekhleb
authored andcommitted
feat: add Deque data structure with tests and README
1 parent 0ae5dd8 commit 6bb32ed

3 files changed

Lines changed: 371 additions & 0 deletions

File tree

src/data-structures/deque/Deque.js

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import LinkedList from '../linked-list/LinkedList';
2+
3+
export default class Deque {
4+
constructor() {
5+
// We use a doubly linked list internally so that both front and back
6+
// operations run in O(1) time.
7+
this.linkedList = new LinkedList();
8+
}
9+
10+
/**
11+
* Check if the deque is empty.
12+
* @return {boolean}
13+
*/
14+
isEmpty() {
15+
return !this.linkedList.head;
16+
}
17+
18+
/**
19+
* Read the element at the front of the deque without removing it.
20+
* @return {*}
21+
*/
22+
peekFront() {
23+
if (!this.linkedList.head) {
24+
return null;
25+
}
26+
return this.linkedList.head.value;
27+
}
28+
29+
/**
30+
* Read the element at the back of the deque without removing it.
31+
* @return {*}
32+
*/
33+
peekBack() {
34+
if (!this.linkedList.tail) {
35+
return null;
36+
}
37+
return this.linkedList.tail.value;
38+
}
39+
40+
/**
41+
* Add a new element to the front (head) of the deque.
42+
* @param {*} value
43+
*/
44+
addFront(value) {
45+
this.linkedList.prepend(value);
46+
}
47+
48+
/**
49+
* Add a new element to the back (tail) of the deque.
50+
* @param {*} value
51+
*/
52+
addBack(value) {
53+
this.linkedList.append(value);
54+
}
55+
56+
/**
57+
* Remove the element from the front (head) of the deque.
58+
* @return {*}
59+
*/
60+
removeFront() {
61+
const removedHead = this.linkedList.deleteHead();
62+
return removedHead ? removedHead.value : null;
63+
}
64+
65+
/**
66+
* Remove the element from the back (tail) of the deque.
67+
* @return {*}
68+
*/
69+
removeBack() {
70+
const removedTail = this.linkedList.deleteTail();
71+
return removedTail ? removedTail.value : null;
72+
}
73+
74+
/**
75+
* Return the number of elements in the deque.
76+
* @return {number}
77+
*/
78+
get size() {
79+
let count = 0;
80+
let currentNode = this.linkedList.head;
81+
while (currentNode) {
82+
count += 1;
83+
currentNode = currentNode.next;
84+
}
85+
return count;
86+
}
87+
88+
/**
89+
* Convert the deque to an array (front to back).
90+
* @return {*[]}
91+
*/
92+
toArray() {
93+
return this.linkedList
94+
.toArray()
95+
.map((linkedListNode) => linkedListNode.value);
96+
}
97+
98+
/**
99+
* Return a string representation of the deque.
100+
* @param {function} [callback]
101+
* @return {string}
102+
*/
103+
toString(callback) {
104+
return this.linkedList.toString(callback);
105+
}
106+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Deque (Double-Ended Queue)
2+
3+
A **deque** (pronounced "deck", short for **double-ended queue**) is a linear data
4+
structure that generalises both a stack and a queue. Elements can be added or
5+
removed from **either end** — the front (head) or the back (tail) — in **O(1)** time.
6+
7+
```
8+
addFront(3) addBack(4)
9+
↓ ↓
10+
┌───┬───┬───┬───┐
11+
│ 3 │ 1 │ 2 │ 4 │ ← internal linked list
12+
└───┴───┴───┴───┘
13+
↑ ↑
14+
removeFront() removeBack()
15+
```
16+
17+
## Operations
18+
19+
| Method | Description | Time |
20+
| ------------- | -------------------------------------------- | ---- |
21+
| `addFront(v)` | Insert element `v` at the front | O(1) |
22+
| `addBack(v)` | Insert element `v` at the back | O(1) |
23+
| `removeFront()` | Remove and return the front element | O(1) |
24+
| `removeBack()` | Remove and return the back element | O(1) |
25+
| `peekFront()` | Return the front element without removing it | O(1) |
26+
| `peekBack()` | Return the back element without removing it | O(1) |
27+
| `isEmpty()` | Return `true` if the deque has no elements | O(1) |
28+
| `size` | Return the number of elements | O(n) |
29+
30+
> **Note:** `size` is O(n) because the underlying linked list does not cache
31+
> length. If you call `size` frequently, consider maintaining an internal counter.
32+
33+
## Complexity
34+
35+
| | |
36+
| --------- | ---- |
37+
| Space | O(n) |
38+
| addFront | O(1) |
39+
| addBack | O(1) |
40+
| removeFront | O(1) |
41+
| removeBack | O(1) |
42+
| peekFront | O(1) |
43+
| peekBack | O(1) |
44+
45+
## Use Cases
46+
47+
A deque is the right tool when you need **O(1) access at both ends**:
48+
49+
- **Sliding window maximum/minimum** — maintain candidates in a monotonic deque
50+
so each element is pushed and popped at most once (overall O(n)).
51+
- **Browser history** — navigate backward (`removeFront`) and forward
52+
(`removeBack`) through pages.
53+
- **Undo / redo stacks** — push actions to the back, undo from the back,
54+
redo from the front.
55+
- **Palindrome checking** — compare characters from both ends simultaneously.
56+
- **Work-stealing schedulers** (e.g. Java's `ForkJoinPool`) — threads push/pop
57+
from their own back, while idle threads steal from another thread's front.
58+
59+
## Implementation Note
60+
61+
This implementation is backed by the project's existing `LinkedList` (a doubly
62+
linked list). This gives O(1) `prepend` (for `addFront`) and O(1) `append` /
63+
`deleteTail` (for `addBack` / `removeBack`), with no need to shift array
64+
elements.
65+
66+
An alternative implementation using a circular buffer (fixed-size array) offers
67+
better cache locality but requires resizing logic. The linked-list approach is
68+
chosen here to stay consistent with the other data structures in this project.
69+
70+
## References
71+
72+
- [Deque — Wikipedia](https://en.wikipedia.org/wiki/Double-ended_queue)
73+
- [Deque Data Structure — GeeksForGeeks](https://www.geeksforgeeks.org/deque-set-1-introduction-applications/)
74+
- [▶ Deque in 3 minutes — YouTube](https://www.youtube.com/watch?v=kLBuJ1998Do)
75+
- [Sliding Window Maximum using Deque — YouTube](https://www.youtube.com/watch?v=2SXqBsTR6a8)
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
import Deque from '../Deque';
2+
3+
describe('Deque', () => {
4+
it('should create an empty deque', () => {
5+
const deque = new Deque();
6+
7+
expect(deque).not.toBeNull();
8+
expect(deque.isEmpty()).toBe(true);
9+
expect(deque.size).toBe(0);
10+
});
11+
12+
it('should peek at the front and back of an empty deque', () => {
13+
const deque = new Deque();
14+
15+
expect(deque.peekFront()).toBeNull();
16+
expect(deque.peekBack()).toBeNull();
17+
});
18+
19+
it('should return null when removing from an empty deque', () => {
20+
const deque = new Deque();
21+
22+
expect(deque.removeFront()).toBeNull();
23+
expect(deque.removeBack()).toBeNull();
24+
});
25+
26+
it('should add elements to the back and remove from the front (queue behaviour)', () => {
27+
const deque = new Deque();
28+
29+
deque.addBack(1);
30+
deque.addBack(2);
31+
deque.addBack(3);
32+
33+
expect(deque.isEmpty()).toBe(false);
34+
expect(deque.size).toBe(3);
35+
expect(deque.peekFront()).toBe(1);
36+
expect(deque.peekBack()).toBe(3);
37+
38+
expect(deque.removeFront()).toBe(1);
39+
expect(deque.removeFront()).toBe(2);
40+
expect(deque.removeFront()).toBe(3);
41+
expect(deque.removeFront()).toBeNull();
42+
expect(deque.isEmpty()).toBe(true);
43+
});
44+
45+
it('should add elements to the front and remove from the back (reversed queue)', () => {
46+
const deque = new Deque();
47+
48+
deque.addFront(1);
49+
deque.addFront(2);
50+
deque.addFront(3);
51+
52+
expect(deque.size).toBe(3);
53+
expect(deque.peekFront()).toBe(3);
54+
expect(deque.peekBack()).toBe(1);
55+
56+
expect(deque.removeBack()).toBe(1);
57+
expect(deque.removeBack()).toBe(2);
58+
expect(deque.removeBack()).toBe(3);
59+
expect(deque.removeBack()).toBeNull();
60+
expect(deque.isEmpty()).toBe(true);
61+
});
62+
63+
it('should add elements to the front and remove from the front (stack behaviour)', () => {
64+
const deque = new Deque();
65+
66+
deque.addFront('a');
67+
deque.addFront('b');
68+
deque.addFront('c');
69+
70+
expect(deque.peekFront()).toBe('c');
71+
expect(deque.removeFront()).toBe('c');
72+
expect(deque.removeFront()).toBe('b');
73+
expect(deque.removeFront()).toBe('a');
74+
expect(deque.isEmpty()).toBe(true);
75+
});
76+
77+
it('should support mixed addFront and addBack operations', () => {
78+
const deque = new Deque();
79+
80+
// Build: [3, 1, 2, 4]
81+
deque.addBack(1);
82+
deque.addBack(2);
83+
deque.addFront(3);
84+
deque.addBack(4);
85+
86+
expect(deque.size).toBe(4);
87+
expect(deque.peekFront()).toBe(3);
88+
expect(deque.peekBack()).toBe(4);
89+
expect(deque.toArray()).toEqual([3, 1, 2, 4]);
90+
});
91+
92+
it('should support mixed removeFront and removeBack operations', () => {
93+
const deque = new Deque();
94+
95+
deque.addBack(1);
96+
deque.addBack(2);
97+
deque.addBack(3);
98+
deque.addBack(4);
99+
100+
expect(deque.removeFront()).toBe(1);
101+
expect(deque.removeBack()).toBe(4);
102+
expect(deque.removeFront()).toBe(2);
103+
expect(deque.removeBack()).toBe(3);
104+
expect(deque.isEmpty()).toBe(true);
105+
});
106+
107+
it('should handle a single element correctly', () => {
108+
const deque = new Deque();
109+
110+
deque.addBack(42);
111+
112+
expect(deque.size).toBe(1);
113+
expect(deque.peekFront()).toBe(42);
114+
expect(deque.peekBack()).toBe(42);
115+
116+
expect(deque.removeFront()).toBe(42);
117+
expect(deque.isEmpty()).toBe(true);
118+
expect(deque.peekFront()).toBeNull();
119+
expect(deque.peekBack()).toBeNull();
120+
});
121+
122+
it('should handle object values', () => {
123+
const deque = new Deque();
124+
125+
const obj1 = { key: 'value1' };
126+
const obj2 = { key: 'value2' };
127+
128+
deque.addBack(obj1);
129+
deque.addFront(obj2);
130+
131+
expect(deque.peekFront()).toEqual({ key: 'value2' });
132+
expect(deque.peekBack()).toEqual({ key: 'value1' });
133+
expect(deque.removeFront()).toEqual({ key: 'value2' });
134+
expect(deque.removeFront()).toEqual({ key: 'value1' });
135+
});
136+
137+
it('should convert to array correctly', () => {
138+
const deque = new Deque();
139+
140+
expect(deque.toArray()).toEqual([]);
141+
142+
deque.addBack(1);
143+
deque.addBack(2);
144+
deque.addFront(0);
145+
146+
expect(deque.toArray()).toEqual([0, 1, 2]);
147+
});
148+
149+
it('should convert to string correctly', () => {
150+
const deque = new Deque();
151+
152+
deque.addBack(1);
153+
deque.addBack(2);
154+
deque.addBack(3);
155+
156+
expect(deque.toString()).toBe('1,2,3');
157+
});
158+
159+
it('should convert to string with a custom callback', () => {
160+
const deque = new Deque();
161+
162+
deque.addBack({ value: 1, key: 'test1' });
163+
deque.addBack({ value: 2, key: 'test2' });
164+
165+
const toString = (value) => `${value.key}:${value.value}`;
166+
167+
expect(deque.toString(toString)).toBe('test1:1,test2:2');
168+
});
169+
170+
it('should track size correctly after many operations', () => {
171+
const deque = new Deque();
172+
173+
expect(deque.size).toBe(0);
174+
175+
deque.addBack(1);
176+
expect(deque.size).toBe(1);
177+
178+
deque.addFront(0);
179+
expect(deque.size).toBe(2);
180+
181+
deque.removeFront();
182+
expect(deque.size).toBe(1);
183+
184+
deque.removeBack();
185+
expect(deque.size).toBe(0);
186+
187+
deque.removeBack();
188+
expect(deque.size).toBe(0);
189+
});
190+
});

0 commit comments

Comments
 (0)