From d27784228beb75a049f6ace2fec1518f22c9e415 Mon Sep 17 00:00:00 2001 From: Revone Date: Tue, 3 Dec 2024 13:35:25 +1300 Subject: [PATCH] feat: Add maxLen configuration option to all queue-based data structures. docs: Add example code for Deque. --- src/data-structures/binary-tree/avl-tree.ts | 27 +-- .../binary-tree/tree-multi-map.ts | 168 +++++++++++++++++- .../linked-list/doubly-linked-list.ts | 14 ++ .../linked-list/singly-linked-list.ts | 13 ++ src/data-structures/queue/queue.ts | 14 +- .../linked-list/doubly-linked-list.ts | 4 +- .../linked-list/singly-linked-list.ts | 4 +- src/types/data-structures/queue/queue.ts | 1 + .../linked-list/doubly-linked-list.test.ts | 21 ++- .../linked-list/singly-linked-list.test.ts | 10 ++ test/unit/data-structures/queue/deque.test.ts | 55 ++++++ test/unit/data-structures/queue/queue.test.ts | 10 ++ 12 files changed, 315 insertions(+), 26 deletions(-) diff --git a/src/data-structures/binary-tree/avl-tree.ts b/src/data-structures/binary-tree/avl-tree.ts index ad94187..4825b34 100644 --- a/src/data-structures/binary-tree/avl-tree.ts +++ b/src/data-structures/binary-tree/avl-tree.ts @@ -62,6 +62,7 @@ export class AVLTreeNode extends BSTNode { * 7. Path Length: The path length from the root to any leaf is longer compared to an unbalanced BST, but shorter than a linear chain of nodes. * @example * // Find elements in a range + * // In interval queries, AVL trees, with their strictly balanced structure and lower height, offer better query efficiency, making them ideal for frequent and high-performance interval queries. In contrast, Red-Black trees, with lower update costs, are more suitable for scenarios involving frequent insertions and deletions where the requirements for interval queries are less demanding. * type Datum = { timestamp: Date; temperature: number }; * // Fixed dataset of CPU temperature readings * const cpuData: Datum[] = [ @@ -110,19 +111,19 @@ export class AVLTreeNode extends BSTNode { * temperature: cpuTemperatureTree.get(node ? node.key : undefined) * })); * - * console.log(rangeResults); // [ - * // { minute: 5, temperature: 59.4 }, - * // { minute: 6, temperature: 60.1 }, - * // { minute: 7, temperature: 61.3 }, - * // { minute: 8, temperature: 62 }, - * // { minute: 9, temperature: 63.5 }, - * // { minute: 10, temperature: 64 }, - * // { minute: 11, temperature: 62.8 }, - * // { minute: 12, temperature: 61.5 }, - * // { minute: 13, temperature: 60.2 }, - * // { minute: 14, temperature: 59.8 }, - * // { minute: 15, temperature: 58.6 } - * // ] + * console.log(rangeResults); // [ + * // { minute: 5, temperature: 59.4 }, + * // { minute: 6, temperature: 60.1 }, + * // { minute: 7, temperature: 61.3 }, + * // { minute: 8, temperature: 62 }, + * // { minute: 9, temperature: 63.5 }, + * // { minute: 10, temperature: 64 }, + * // { minute: 11, temperature: 62.8 }, + * // { minute: 12, temperature: 61.5 }, + * // { minute: 13, temperature: 60.2 }, + * // { minute: 14, temperature: 59.8 }, + * // { minute: 15, temperature: 58.6 } + * // ] */ export class AVLTree extends BST diff --git a/src/data-structures/binary-tree/tree-multi-map.ts b/src/data-structures/binary-tree/tree-multi-map.ts index ed86e5c..06f8b97 100644 --- a/src/data-structures/binary-tree/tree-multi-map.ts +++ b/src/data-structures/binary-tree/tree-multi-map.ts @@ -55,11 +55,169 @@ export class TreeMultiMapNode extends RedBlackTreeNode /** * * @example - * // Find elements in a range - * const tmm = new TreeMultiMap([10, 5, 15, 3, 7, 12, 18]); - * console.log(tmm.search(new Range(5, 10))); // [5, 7, 10] - * console.log(tmm.search(new Range(4, 12))); // [5, 7, 10, 12] - * console.log(tmm.search(new Range(15, 20))); // [15, 18] + * // players ranked by score with their equipment + * type Equipment = { + * name: string; // Equipment name + * quality: 'legendary' | 'epic' | 'rare' | 'common'; + * level: number; + * }; + * + * type Player = { + * name: string; + * score: number; + * equipments: Equipment[]; + * }; + * + * // Mock player data with their scores and equipment + * const players: Player[] = [ + * { + * name: 'DragonSlayer', + * score: 8750, + * equipments: [ + * { name: 'AWM', quality: 'legendary', level: 85 }, + * { name: 'Level 3 Helmet', quality: 'epic', level: 80 }, + * { name: 'Extended Quickdraw Mag', quality: 'rare', level: 75 }, + * { name: 'Compensator', quality: 'epic', level: 78 }, + * { name: 'Vertical Grip', quality: 'rare', level: 72 } + * ] + * }, + * { + * name: 'ShadowNinja', + * score: 7200, + * equipments: [ + * { name: 'M416', quality: 'epic', level: 75 }, + * { name: 'Ghillie Suit', quality: 'rare', level: 70 }, + * { name: 'Red Dot Sight', quality: 'common', level: 65 }, + * { name: 'Extended QuickDraw Mag', quality: 'rare', level: 68 } + * ] + * }, + * { + * name: 'RuneMaster', + * score: 9100, + * equipments: [ + * { name: 'KAR98K', quality: 'legendary', level: 90 }, + * { name: 'Level 3 Vest', quality: 'legendary', level: 85 }, + * { name: 'Holographic Sight', quality: 'epic', level: 82 }, + * { name: 'Suppressor', quality: 'legendary', level: 88 }, + * { name: 'Level 3 Backpack', quality: 'epic', level: 80 } + * ] + * }, + * { + * name: 'BattleKing', + * score: 8500, + * equipments: [ + * { name: 'AUG', quality: 'epic', level: 82 }, + * { name: 'Red Dot Sight', quality: 'rare', level: 75 }, + * { name: 'Extended Mag', quality: 'common', level: 70 }, + * { name: 'Tactical Stock', quality: 'rare', level: 76 } + * ] + * }, + * { + * name: 'SniperElite', + * score: 7800, + * equipments: [ + * { name: 'M24', quality: 'legendary', level: 88 }, + * { name: 'Compensator', quality: 'epic', level: 80 }, + * { name: 'Scope 8x', quality: 'legendary', level: 85 }, + * { name: 'Level 2 Helmet', quality: 'rare', level: 75 } + * ] + * }, + * { + * name: 'RushMaster', + * score: 7500, + * equipments: [ + * { name: 'Vector', quality: 'rare', level: 72 }, + * { name: 'Level 2 Helmet', quality: 'common', level: 65 }, + * { name: 'Quickdraw Mag', quality: 'common', level: 60 }, + * { name: 'Laser Sight', quality: 'rare', level: 68 } + * ] + * }, + * { + * name: 'GhostWarrior', + * score: 8200, + * equipments: [ + * { name: 'SCAR-L', quality: 'epic', level: 78 }, + * { name: 'Extended Quickdraw Mag', quality: 'rare', level: 70 }, + * { name: 'Holographic Sight', quality: 'epic', level: 75 }, + * { name: 'Suppressor', quality: 'rare', level: 72 }, + * { name: 'Vertical Grip', quality: 'common', level: 65 } + * ] + * }, + * { + * name: 'DeathDealer', + * score: 7300, + * equipments: [ + * { name: 'SKS', quality: 'epic', level: 76 }, + * { name: 'Holographic Sight', quality: 'rare', level: 68 }, + * { name: 'Extended Mag', quality: 'common', level: 65 } + * ] + * }, + * { + * name: 'StormRider', + * score: 8900, + * equipments: [ + * { name: 'MK14', quality: 'legendary', level: 92 }, + * { name: 'Level 3 Backpack', quality: 'legendary', level: 85 }, + * { name: 'Scope 8x', quality: 'epic', level: 80 }, + * { name: 'Suppressor', quality: 'legendary', level: 88 }, + * { name: 'Tactical Stock', quality: 'rare', level: 75 } + * ] + * }, + * { + * name: 'CombatLegend', + * score: 7600, + * equipments: [ + * { name: 'UMP45', quality: 'rare', level: 74 }, + * { name: 'Level 2 Vest', quality: 'common', level: 67 }, + * { name: 'Red Dot Sight', quality: 'common', level: 62 }, + * { name: 'Extended Mag', quality: 'rare', level: 70 } + * ] + * } + * ]; + * + * // Create a TreeMultiMap for player rankings + * const playerRankings = new TreeMultiMap(players, { + * toEntryFn: ({ score, equipments }) => [score, equipments], + * isMapMode: false + * }); + * + * const topPlayersEquipments = playerRankings.rangeSearch([8900, 10000], node => playerRankings.get(node)); + * console.log(topPlayersEquipments); // [ + * // [ + * // { + * // name: 'MK14', + * // quality: 'legendary', + * // level: 92 + * // }, + * // { name: 'Level 3 Backpack', quality: 'legendary', level: 85 }, + * // { + * // name: 'Scope 8x', + * // quality: 'epic', + * // level: 80 + * // }, + * // { name: 'Suppressor', quality: 'legendary', level: 88 }, + * // { + * // name: 'Tactical Stock', + * // quality: 'rare', + * // level: 75 + * // } + * // ], + * // [ + * // { name: 'KAR98K', quality: 'legendary', level: 90 }, + * // { + * // name: 'Level 3 Vest', + * // quality: 'legendary', + * // level: 85 + * // }, + * // { name: 'Holographic Sight', quality: 'epic', level: 82 }, + * // { + * // name: 'Suppressor', + * // quality: 'legendary', + * // level: 88 + * // }, + * // { name: 'Level 3 Backpack', quality: 'epic', level: 80 } + * // ] + * // ] */ export class TreeMultiMap extends RedBlackTree diff --git a/src/data-structures/linked-list/doubly-linked-list.ts b/src/data-structures/linked-list/doubly-linked-list.ts index 62a4620..116a535 100644 --- a/src/data-structures/linked-list/doubly-linked-list.ts +++ b/src/data-structures/linked-list/doubly-linked-list.ts @@ -532,6 +532,12 @@ export class DoublyLinkedList extends IterableElementBase 0 && maxLen % 1 === 0) this._maxLen = maxLen; + } + this.pushMany(elements); } @@ -566,6 +572,12 @@ export class DoublyLinkedList extends IterableElementBase extends IterableElementBase 0 && this.size > this._maxLen) this.shift(); return true; } @@ -707,6 +720,7 @@ export class DoublyLinkedList extends IterableElementBase 0 && this._size > this._maxLen) this.pop(); return true; } diff --git a/src/data-structures/linked-list/singly-linked-list.ts b/src/data-structures/linked-list/singly-linked-list.ts index 6fc4e84..f8cdd0a 100644 --- a/src/data-structures/linked-list/singly-linked-list.ts +++ b/src/data-structures/linked-list/singly-linked-list.ts @@ -68,6 +68,12 @@ export class SinglyLinkedList extends IterableElementBase ) { super(options); + + if (options) { + const { maxLen } = options; + if (typeof maxLen === 'number' && maxLen > 0 && maxLen % 1 === 0) this._maxLen = maxLen; + } + this.pushMany(elements); } @@ -109,6 +115,12 @@ export class SinglyLinkedList extends IterableElementBase extends IterableElementBase 0 && this.size > this._maxLen) this.shift(); return true; } diff --git a/src/data-structures/queue/queue.ts b/src/data-structures/queue/queue.ts index fd12a37..09ad72c 100644 --- a/src/data-structures/queue/queue.ts +++ b/src/data-structures/queue/queue.ts @@ -21,8 +21,9 @@ export class Queue extends IterableElementBase 0 && maxLen % 1 === 0) this._maxLen = maxLen; } this.pushMany(elements); @@ -30,14 +31,16 @@ export class Queue extends IterableElementBase extends IterableElementBase 0 && this.size > this._maxLen) this.shift(); return true; } diff --git a/src/types/data-structures/linked-list/doubly-linked-list.ts b/src/types/data-structures/linked-list/doubly-linked-list.ts index 49b7f44..5883a71 100644 --- a/src/types/data-structures/linked-list/doubly-linked-list.ts +++ b/src/types/data-structures/linked-list/doubly-linked-list.ts @@ -1,3 +1,5 @@ import { IterableElementBaseOptions } from '../base'; -export type DoublyLinkedListOptions = IterableElementBaseOptions & {}; +export type DoublyLinkedListOptions = IterableElementBaseOptions & { + maxLen?: number; +}; diff --git a/src/types/data-structures/linked-list/singly-linked-list.ts b/src/types/data-structures/linked-list/singly-linked-list.ts index c3e4c3e..200f84c 100644 --- a/src/types/data-structures/linked-list/singly-linked-list.ts +++ b/src/types/data-structures/linked-list/singly-linked-list.ts @@ -1,3 +1,5 @@ import { IterableElementBaseOptions } from '../base'; -export type SinglyLinkedListOptions = IterableElementBaseOptions & {}; +export type SinglyLinkedListOptions = IterableElementBaseOptions & { + maxLen?: number; +}; diff --git a/src/types/data-structures/queue/queue.ts b/src/types/data-structures/queue/queue.ts index eb60615..75185f5 100644 --- a/src/types/data-structures/queue/queue.ts +++ b/src/types/data-structures/queue/queue.ts @@ -2,4 +2,5 @@ import { IterableElementBaseOptions } from '../base'; export type QueueOptions = IterableElementBaseOptions & { autoCompactRatio?: number; + maxLen?: number; }; diff --git a/test/unit/data-structures/linked-list/doubly-linked-list.test.ts b/test/unit/data-structures/linked-list/doubly-linked-list.test.ts index 0c20f83..ca7abf6 100644 --- a/test/unit/data-structures/linked-list/doubly-linked-list.test.ts +++ b/test/unit/data-structures/linked-list/doubly-linked-list.test.ts @@ -1,4 +1,4 @@ -import { DoublyLinkedList, DoublyLinkedListNode } from '../../../../src'; +import { DoublyLinkedList, DoublyLinkedListNode, SinglyLinkedList } from '../../../../src'; describe('DoublyLinkedListNode', () => { it('should DoublyLinkedListNode', () => { @@ -124,6 +124,25 @@ describe('DoublyLinkedList Operation Test', () => { expect(list.tail!.value).toBe(3); }); + it('push with maxLen', () => { + const list = new DoublyLinkedList([], { maxLen: 10 }); + for (let i = 0; i < 1000; i++) { + list.push(i); + } + expect(list.maxLen).toBe(10); + expect(list.size).toBe(10); + expect(list.first).toBe(990); + + list.clear(); + for (let i = 0; i < 1000; i++) { + list.unshift(i); + } + + expect(list.maxLen).toBe(10); + expect(list.size).toBe(10); + expect(list.last).toBe(990); + }); + it('should pop elements from the end of the list', () => { list.push(1); list.push(2); diff --git a/test/unit/data-structures/linked-list/singly-linked-list.test.ts b/test/unit/data-structures/linked-list/singly-linked-list.test.ts index 1e22481..b3ace4f 100644 --- a/test/unit/data-structures/linked-list/singly-linked-list.test.ts +++ b/test/unit/data-structures/linked-list/singly-linked-list.test.ts @@ -37,6 +37,16 @@ describe('SinglyLinkedList Operation Test', () => { list.push(2); expect(list.toArray()).toEqual([1, 2]); }); + + it('push with maxLen', () => { + const list = new SinglyLinkedList([], { maxLen: 10 }); + for (let i = 0; i < 1000; i++) { + list.push(i); + } + expect(list.maxLen).toBe(10); + expect(list.size).toBe(10); + expect(list.first).toBe(990); + }); }); describe('pop', () => { diff --git a/test/unit/data-structures/queue/deque.test.ts b/test/unit/data-structures/queue/deque.test.ts index ed86eb9..06e98e0 100644 --- a/test/unit/data-structures/queue/deque.test.ts +++ b/test/unit/data-structures/queue/deque.test.ts @@ -722,3 +722,58 @@ describe('Deque', () => { ]); // TODO may be a problem }); }); + +describe('classic uses', () => { + it('@example prize roulette', () => { + class PrizeRoulette { + private deque: Deque; + + constructor(prizes: string[]) { + // Initialize the deque with prizes + this.deque = new Deque(prizes); + } + + // Rotate clockwise to the right (forward) + rotateClockwise(steps: number): void { + const n = this.deque.size; + if (n === 0) return; + + for (let i = 0; i < steps; i++) { + const last = this.deque.pop(); // Remove the last element + this.deque.unshift(last!); // Add it to the front + } + } + + // Rotate counterclockwise to the left (backward) + rotateCounterClockwise(steps: number): void { + const n = this.deque.size; + if (n === 0) return; + + for (let i = 0; i < steps; i++) { + const first = this.deque.shift(); // Remove the first element + this.deque.push(first!); // Add it to the back + } + } + + // Display the current prize at the head + display() { + return this.deque.first; + } + } + + // Example usage + const prizes = ['Car', 'Bike', 'Laptop', 'Phone', 'Watch', 'Headphones']; // Initialize the prize list + const roulette = new PrizeRoulette(prizes); + + // Display the initial state + expect(roulette.display()).toBe('Car'); // Car + + // Rotate clockwise by 3 steps + roulette.rotateClockwise(3); + expect(roulette.display()).toBe('Phone'); // Phone + + // Rotate counterclockwise by 2 steps + roulette.rotateCounterClockwise(2); + expect(roulette.display()).toBe('Headphones'); // Headphones + }); +}); diff --git a/test/unit/data-structures/queue/queue.test.ts b/test/unit/data-structures/queue/queue.test.ts index 6dd4ef5..e726e14 100644 --- a/test/unit/data-structures/queue/queue.test.ts +++ b/test/unit/data-structures/queue/queue.test.ts @@ -90,6 +90,16 @@ describe('Queue', () => { expect(queue.isEmpty()).toBeTruthy(); }); + it('push with maxLen', () => { + const queue = new Queue([], { maxLen: 10 }); + for (let i = 0; i < 1000; i++) { + queue.push(i); + } + expect(queue.maxLen).toBe(10); + expect(queue.size).toBe(10); + expect(queue.first).toBe(990); + }); + it('compact method should work well', () => { for (let i = 0; i < 1000; i++) queue.push(i);