From 6bb32edff29312d0c574c1295a02f23bd5d59da6 Mon Sep 17 00:00:00 2001 From: shahidansari311 Date: Tue, 5 May 2026 03:51:35 +0530 Subject: [PATCH 1/2] feat: add Deque data structure with tests and README --- src/data-structures/deque/Deque.js | 106 ++++++++++ src/data-structures/deque/README.md | 75 +++++++ .../deque/__test__/Deque.test.js | 190 ++++++++++++++++++ 3 files changed, 371 insertions(+) create mode 100644 src/data-structures/deque/Deque.js create mode 100644 src/data-structures/deque/README.md create mode 100644 src/data-structures/deque/__test__/Deque.test.js diff --git a/src/data-structures/deque/Deque.js b/src/data-structures/deque/Deque.js new file mode 100644 index 000000000..7487ec6fe --- /dev/null +++ b/src/data-structures/deque/Deque.js @@ -0,0 +1,106 @@ +import LinkedList from '../linked-list/LinkedList'; + +export default class Deque { + constructor() { + // We use a doubly linked list internally so that both front and back + // operations run in O(1) time. + this.linkedList = new LinkedList(); + } + + /** + * Check if the deque is empty. + * @return {boolean} + */ + isEmpty() { + return !this.linkedList.head; + } + + /** + * Read the element at the front of the deque without removing it. + * @return {*} + */ + peekFront() { + if (!this.linkedList.head) { + return null; + } + return this.linkedList.head.value; + } + + /** + * Read the element at the back of the deque without removing it. + * @return {*} + */ + peekBack() { + if (!this.linkedList.tail) { + return null; + } + return this.linkedList.tail.value; + } + + /** + * Add a new element to the front (head) of the deque. + * @param {*} value + */ + addFront(value) { + this.linkedList.prepend(value); + } + + /** + * Add a new element to the back (tail) of the deque. + * @param {*} value + */ + addBack(value) { + this.linkedList.append(value); + } + + /** + * Remove the element from the front (head) of the deque. + * @return {*} + */ + removeFront() { + const removedHead = this.linkedList.deleteHead(); + return removedHead ? removedHead.value : null; + } + + /** + * Remove the element from the back (tail) of the deque. + * @return {*} + */ + removeBack() { + const removedTail = this.linkedList.deleteTail(); + return removedTail ? removedTail.value : null; + } + + /** + * Return the number of elements in the deque. + * @return {number} + */ + get size() { + let count = 0; + let currentNode = this.linkedList.head; + while (currentNode) { + count += 1; + currentNode = currentNode.next; + } + return count; + } + + /** + * Convert the deque to an array (front to back). + * @return {*[]} + */ + toArray() { + return this.linkedList + .toArray() + .map((linkedListNode) => linkedListNode.value); + } + + /** + * Return a string representation of the deque. + * @param {function} [callback] + * @return {string} + */ + toString(callback) { + return this.linkedList.toString(callback); + } +} diff --git a/src/data-structures/deque/README.md b/src/data-structures/deque/README.md new file mode 100644 index 000000000..29d5be51c --- /dev/null +++ b/src/data-structures/deque/README.md @@ -0,0 +1,75 @@ +# Deque (Double-Ended Queue) + +A **deque** (pronounced "deck", short for **double-ended queue**) is a linear data +structure that generalises both a stack and a queue. Elements can be added or +removed from **either end** — the front (head) or the back (tail) — in **O(1)** time. + +``` +addFront(3) addBack(4) + ↓ ↓ +┌───┬───┬───┬───┐ +│ 3 │ 1 │ 2 │ 4 │ ← internal linked list +└───┴───┴───┴───┘ + ↑ ↑ +removeFront() removeBack() +``` + +## Operations + +| Method | Description | Time | +| ------------- | -------------------------------------------- | ---- | +| `addFront(v)` | Insert element `v` at the front | O(1) | +| `addBack(v)` | Insert element `v` at the back | O(1) | +| `removeFront()` | Remove and return the front element | O(1) | +| `removeBack()` | Remove and return the back element | O(1) | +| `peekFront()` | Return the front element without removing it | O(1) | +| `peekBack()` | Return the back element without removing it | O(1) | +| `isEmpty()` | Return `true` if the deque has no elements | O(1) | +| `size` | Return the number of elements | O(n) | + +> **Note:** `size` is O(n) because the underlying linked list does not cache +> length. If you call `size` frequently, consider maintaining an internal counter. + +## Complexity + +| | | +| --------- | ---- | +| Space | O(n) | +| addFront | O(1) | +| addBack | O(1) | +| removeFront | O(1) | +| removeBack | O(1) | +| peekFront | O(1) | +| peekBack | O(1) | + +## Use Cases + +A deque is the right tool when you need **O(1) access at both ends**: + +- **Sliding window maximum/minimum** — maintain candidates in a monotonic deque + so each element is pushed and popped at most once (overall O(n)). +- **Browser history** — navigate backward (`removeFront`) and forward + (`removeBack`) through pages. +- **Undo / redo stacks** — push actions to the back, undo from the back, + redo from the front. +- **Palindrome checking** — compare characters from both ends simultaneously. +- **Work-stealing schedulers** (e.g. Java's `ForkJoinPool`) — threads push/pop + from their own back, while idle threads steal from another thread's front. + +## Implementation Note + +This implementation is backed by the project's existing `LinkedList` (a doubly +linked list). This gives O(1) `prepend` (for `addFront`) and O(1) `append` / +`deleteTail` (for `addBack` / `removeBack`), with no need to shift array +elements. + +An alternative implementation using a circular buffer (fixed-size array) offers +better cache locality but requires resizing logic. The linked-list approach is +chosen here to stay consistent with the other data structures in this project. + +## References + +- [Deque — Wikipedia](https://en.wikipedia.org/wiki/Double-ended_queue) +- [Deque Data Structure — GeeksForGeeks](https://www.geeksforgeeks.org/deque-set-1-introduction-applications/) +- [▶ Deque in 3 minutes — YouTube](https://www.youtube.com/watch?v=kLBuJ1998Do) +- [Sliding Window Maximum using Deque — YouTube](https://www.youtube.com/watch?v=2SXqBsTR6a8) \ No newline at end of file diff --git a/src/data-structures/deque/__test__/Deque.test.js b/src/data-structures/deque/__test__/Deque.test.js new file mode 100644 index 000000000..fc191f1c2 --- /dev/null +++ b/src/data-structures/deque/__test__/Deque.test.js @@ -0,0 +1,190 @@ +import Deque from '../Deque'; + +describe('Deque', () => { + it('should create an empty deque', () => { + const deque = new Deque(); + + expect(deque).not.toBeNull(); + expect(deque.isEmpty()).toBe(true); + expect(deque.size).toBe(0); + }); + + it('should peek at the front and back of an empty deque', () => { + const deque = new Deque(); + + expect(deque.peekFront()).toBeNull(); + expect(deque.peekBack()).toBeNull(); + }); + + it('should return null when removing from an empty deque', () => { + const deque = new Deque(); + + expect(deque.removeFront()).toBeNull(); + expect(deque.removeBack()).toBeNull(); + }); + + it('should add elements to the back and remove from the front (queue behaviour)', () => { + const deque = new Deque(); + + deque.addBack(1); + deque.addBack(2); + deque.addBack(3); + + expect(deque.isEmpty()).toBe(false); + expect(deque.size).toBe(3); + expect(deque.peekFront()).toBe(1); + expect(deque.peekBack()).toBe(3); + + expect(deque.removeFront()).toBe(1); + expect(deque.removeFront()).toBe(2); + expect(deque.removeFront()).toBe(3); + expect(deque.removeFront()).toBeNull(); + expect(deque.isEmpty()).toBe(true); + }); + + it('should add elements to the front and remove from the back (reversed queue)', () => { + const deque = new Deque(); + + deque.addFront(1); + deque.addFront(2); + deque.addFront(3); + + expect(deque.size).toBe(3); + expect(deque.peekFront()).toBe(3); + expect(deque.peekBack()).toBe(1); + + expect(deque.removeBack()).toBe(1); + expect(deque.removeBack()).toBe(2); + expect(deque.removeBack()).toBe(3); + expect(deque.removeBack()).toBeNull(); + expect(deque.isEmpty()).toBe(true); + }); + + it('should add elements to the front and remove from the front (stack behaviour)', () => { + const deque = new Deque(); + + deque.addFront('a'); + deque.addFront('b'); + deque.addFront('c'); + + expect(deque.peekFront()).toBe('c'); + expect(deque.removeFront()).toBe('c'); + expect(deque.removeFront()).toBe('b'); + expect(deque.removeFront()).toBe('a'); + expect(deque.isEmpty()).toBe(true); + }); + + it('should support mixed addFront and addBack operations', () => { + const deque = new Deque(); + + // Build: [3, 1, 2, 4] + deque.addBack(1); + deque.addBack(2); + deque.addFront(3); + deque.addBack(4); + + expect(deque.size).toBe(4); + expect(deque.peekFront()).toBe(3); + expect(deque.peekBack()).toBe(4); + expect(deque.toArray()).toEqual([3, 1, 2, 4]); + }); + + it('should support mixed removeFront and removeBack operations', () => { + const deque = new Deque(); + + deque.addBack(1); + deque.addBack(2); + deque.addBack(3); + deque.addBack(4); + + expect(deque.removeFront()).toBe(1); + expect(deque.removeBack()).toBe(4); + expect(deque.removeFront()).toBe(2); + expect(deque.removeBack()).toBe(3); + expect(deque.isEmpty()).toBe(true); + }); + + it('should handle a single element correctly', () => { + const deque = new Deque(); + + deque.addBack(42); + + expect(deque.size).toBe(1); + expect(deque.peekFront()).toBe(42); + expect(deque.peekBack()).toBe(42); + + expect(deque.removeFront()).toBe(42); + expect(deque.isEmpty()).toBe(true); + expect(deque.peekFront()).toBeNull(); + expect(deque.peekBack()).toBeNull(); + }); + + it('should handle object values', () => { + const deque = new Deque(); + + const obj1 = { key: 'value1' }; + const obj2 = { key: 'value2' }; + + deque.addBack(obj1); + deque.addFront(obj2); + + expect(deque.peekFront()).toEqual({ key: 'value2' }); + expect(deque.peekBack()).toEqual({ key: 'value1' }); + expect(deque.removeFront()).toEqual({ key: 'value2' }); + expect(deque.removeFront()).toEqual({ key: 'value1' }); + }); + + it('should convert to array correctly', () => { + const deque = new Deque(); + + expect(deque.toArray()).toEqual([]); + + deque.addBack(1); + deque.addBack(2); + deque.addFront(0); + + expect(deque.toArray()).toEqual([0, 1, 2]); + }); + + it('should convert to string correctly', () => { + const deque = new Deque(); + + deque.addBack(1); + deque.addBack(2); + deque.addBack(3); + + expect(deque.toString()).toBe('1,2,3'); + }); + + it('should convert to string with a custom callback', () => { + const deque = new Deque(); + + deque.addBack({ value: 1, key: 'test1' }); + deque.addBack({ value: 2, key: 'test2' }); + + const toString = (value) => `${value.key}:${value.value}`; + + expect(deque.toString(toString)).toBe('test1:1,test2:2'); + }); + + it('should track size correctly after many operations', () => { + const deque = new Deque(); + + expect(deque.size).toBe(0); + + deque.addBack(1); + expect(deque.size).toBe(1); + + deque.addFront(0); + expect(deque.size).toBe(2); + + deque.removeFront(); + expect(deque.size).toBe(1); + + deque.removeBack(); + expect(deque.size).toBe(0); + + deque.removeBack(); + expect(deque.size).toBe(0); + }); +}); From 7563da8c1a673a8d30d24b86a347adc2688d2fac Mon Sep 17 00:00:00 2001 From: Oleksii Trekhleb <3000285+trekhleb@users.noreply.github.com> Date: Fri, 19 Jun 2026 11:15:58 -0700 Subject: [PATCH 2/2] Deque: use DoublyLinkedList so both ends are truly O(1) --- README.md | 1 + src/data-structures/deque/Deque.js | 31 +++--- src/data-structures/deque/README.md | 150 ++++++++++++++-------------- 3 files changed, 95 insertions(+), 87 deletions(-) diff --git a/README.md b/README.md index 560b39604..4ffd890cf 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ Remember that each data has its own trade-offs. And you need to pay attention mo * `B` [Doubly Linked List](src/data-structures/doubly-linked-list) * `B` [Queue](src/data-structures/queue) * `B` [Stack](src/data-structures/stack) +* `B` [Deque](src/data-structures/deque) - double-ended queue * `B` [Hash Table](src/data-structures/hash-table) * `B` [Heap](src/data-structures/heap) - max and min heap versions * `B` [Priority Queue](src/data-structures/priority-queue) diff --git a/src/data-structures/deque/Deque.js b/src/data-structures/deque/Deque.js index 7487ec6fe..26b6643c0 100644 --- a/src/data-structures/deque/Deque.js +++ b/src/data-structures/deque/Deque.js @@ -1,10 +1,13 @@ -import LinkedList from '../linked-list/LinkedList'; +import DoublyLinkedList from '../doubly-linked-list/DoublyLinkedList'; export default class Deque { constructor() { // We use a doubly linked list internally so that both front and back - // operations run in O(1) time. - this.linkedList = new LinkedList(); + // operations (add, remove and peek) run in O(1) time. + this.linkedList = new DoublyLinkedList(); + + // Keep a running element count so that `size` stays O(1). + this.length = 0; } /** @@ -43,6 +46,7 @@ export default class Deque { */ addFront(value) { this.linkedList.prepend(value); + this.length += 1; } /** @@ -51,6 +55,7 @@ export default class Deque { */ addBack(value) { this.linkedList.append(value); + this.length += 1; } /** @@ -59,7 +64,11 @@ export default class Deque { */ removeFront() { const removedHead = this.linkedList.deleteHead(); - return removedHead ? removedHead.value : null; + if (!removedHead) { + return null; + } + this.length -= 1; + return removedHead.value; } /** @@ -68,7 +77,11 @@ export default class Deque { */ removeBack() { const removedTail = this.linkedList.deleteTail(); - return removedTail ? removedTail.value : null; + if (!removedTail) { + return null; + } + this.length -= 1; + return removedTail.value; } /** @@ -76,13 +89,7 @@ export default class Deque { * @return {number} */ get size() { - let count = 0; - let currentNode = this.linkedList.head; - while (currentNode) { - count += 1; - currentNode = currentNode.next; - } - return count; + return this.length; } /** diff --git a/src/data-structures/deque/README.md b/src/data-structures/deque/README.md index 29d5be51c..622e54b28 100644 --- a/src/data-structures/deque/README.md +++ b/src/data-structures/deque/README.md @@ -1,75 +1,75 @@ -# Deque (Double-Ended Queue) - -A **deque** (pronounced "deck", short for **double-ended queue**) is a linear data -structure that generalises both a stack and a queue. Elements can be added or -removed from **either end** — the front (head) or the back (tail) — in **O(1)** time. - -``` -addFront(3) addBack(4) - ↓ ↓ -┌───┬───┬───┬───┐ -│ 3 │ 1 │ 2 │ 4 │ ← internal linked list -└───┴───┴───┴───┘ - ↑ ↑ -removeFront() removeBack() -``` - -## Operations - -| Method | Description | Time | -| ------------- | -------------------------------------------- | ---- | -| `addFront(v)` | Insert element `v` at the front | O(1) | -| `addBack(v)` | Insert element `v` at the back | O(1) | -| `removeFront()` | Remove and return the front element | O(1) | -| `removeBack()` | Remove and return the back element | O(1) | -| `peekFront()` | Return the front element without removing it | O(1) | -| `peekBack()` | Return the back element without removing it | O(1) | -| `isEmpty()` | Return `true` if the deque has no elements | O(1) | -| `size` | Return the number of elements | O(n) | - -> **Note:** `size` is O(n) because the underlying linked list does not cache -> length. If you call `size` frequently, consider maintaining an internal counter. - -## Complexity - -| | | -| --------- | ---- | -| Space | O(n) | -| addFront | O(1) | -| addBack | O(1) | -| removeFront | O(1) | -| removeBack | O(1) | -| peekFront | O(1) | -| peekBack | O(1) | - -## Use Cases - -A deque is the right tool when you need **O(1) access at both ends**: - -- **Sliding window maximum/minimum** — maintain candidates in a monotonic deque - so each element is pushed and popped at most once (overall O(n)). -- **Browser history** — navigate backward (`removeFront`) and forward - (`removeBack`) through pages. -- **Undo / redo stacks** — push actions to the back, undo from the back, - redo from the front. -- **Palindrome checking** — compare characters from both ends simultaneously. -- **Work-stealing schedulers** (e.g. Java's `ForkJoinPool`) — threads push/pop - from their own back, while idle threads steal from another thread's front. - -## Implementation Note - -This implementation is backed by the project's existing `LinkedList` (a doubly -linked list). This gives O(1) `prepend` (for `addFront`) and O(1) `append` / -`deleteTail` (for `addBack` / `removeBack`), with no need to shift array -elements. - -An alternative implementation using a circular buffer (fixed-size array) offers -better cache locality but requires resizing logic. The linked-list approach is -chosen here to stay consistent with the other data structures in this project. - -## References - -- [Deque — Wikipedia](https://en.wikipedia.org/wiki/Double-ended_queue) -- [Deque Data Structure — GeeksForGeeks](https://www.geeksforgeeks.org/deque-set-1-introduction-applications/) -- [▶ Deque in 3 minutes — YouTube](https://www.youtube.com/watch?v=kLBuJ1998Do) -- [Sliding Window Maximum using Deque — YouTube](https://www.youtube.com/watch?v=2SXqBsTR6a8) \ No newline at end of file +# Deque (Double-Ended Queue) + +A **deque** (pronounced "deck", short for **double-ended queue**) is a linear data +structure that generalizes both a stack and a queue. Elements can be added or +removed from **either end** — the front (head) or the back (tail) — in **O(1)** time. + +``` + addFront(3) addBack(4) + ↓ ↓ + ┌───────┬───────┬───────┬───────┐ + │ 3 │ 1 │ 2 │ 4 │ ← internal doubly linked list + └───────┴───────┴───────┴───────┘ + ↑ ↑ + removeFront() removeBack() +``` + +## Operations + +| Method | Description | Time | +| --------------- | -------------------------------------------- | ---- | +| `addFront(v)` | Insert element `v` at the front | O(1) | +| `addBack(v)` | Insert element `v` at the back | O(1) | +| `removeFront()` | Remove and return the front element | O(1) | +| `removeBack()` | Remove and return the back element | O(1) | +| `peekFront()` | Return the front element without removing it | O(1) | +| `peekBack()` | Return the back element without removing it | O(1) | +| `isEmpty()` | Return `true` if the deque has no elements | O(1) | +| `size` | Return the number of elements | O(1) | + +> **Note:** `size` is O(1) because the deque keeps a running element count that +> is updated on every add and remove operation. + +## Complexity + +| Operation | Time | +| ---------------------------- | ---- | +| `addFront` / `addBack` | O(1) | +| `removeFront` / `removeBack` | O(1) | +| `peekFront` / `peekBack` | O(1) | +| `isEmpty` / `size` | O(1) | +| Space | O(n) | + +## Use Cases + +A deque is the right tool when you need **O(1) access at both ends**: + +- **Sliding window maximum/minimum** — maintain candidates in a monotonic deque + so each element is pushed and popped at most once (overall O(n)). +- **Browser history** — navigate backward (`removeFront`) and forward + (`removeBack`) through pages. +- **Undo / redo stacks** — push actions to the back, undo from the back, + redo from the front. +- **Palindrome checking** — compare characters from both ends simultaneously. +- **Work-stealing schedulers** (e.g. Java's `ForkJoinPool`) — threads push/pop + from their own back, while idle threads steal from another thread's front. + +## Implementation Note + +This implementation is backed by the project's existing +[`DoublyLinkedList`](../doubly-linked-list). Because every node keeps a reference +to both its previous and next neighbours, this gives O(1) `prepend` (for +`addFront`), O(1) `deleteHead` (for `removeFront`) and O(1) `append` / +`deleteTail` (for `addBack` / `removeBack`), with no need to shift array +elements. The number of elements is tracked in a counter, so `size` is O(1) too. + +An alternative implementation using a circular buffer (fixed-size array) offers +better cache locality but requires resizing logic. The linked-list approach is +chosen here to stay consistent with the other data structures in this project. + +## References + +- [Deque — Wikipedia](https://en.wikipedia.org/wiki/Double-ended_queue) +- [Deque Data Structure — GeeksForGeeks](https://www.geeksforgeeks.org/deque-set-1-introduction-applications/) +- [▶ Deque (Double-Ended Queue) — YouTube](https://www.youtube.com/watch?v=pqg0SOPRlJ4) +- [▶ Sliding Window Maximum using Deque — YouTube](https://www.youtube.com/watch?v=2SXqBsTR6a8)