feat: Add maxLen configuration option to all queue-based data structures.

docs: Add example code for Deque.
This commit is contained in:
Revone 2024-12-03 13:35:25 +13:00
parent 670225e775
commit d27784228b
12 changed files with 315 additions and 26 deletions

View file

@ -62,6 +62,7 @@ export class AVLTreeNode<K = any, V = any> extends BSTNode<K, V> {
* 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<K = any, V = any> extends BSTNode<K, V> {
* 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<K = any, V = any, R = object, MK = any, MV = any, MR = object>
extends BST<K, V, R, MK, MV, MR>

View file

@ -55,11 +55,169 @@ export class TreeMultiMapNode<K = any, V = any> extends RedBlackTreeNode<K, V[]>
/**
*
* @example
* // Find elements in a range
* const tmm = new TreeMultiMap<number>([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<number, Equipment, Player>(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<K = any, V = any, R = object, MK = any, MV = any, MR = object>
extends RedBlackTree<K, V[], R, MK, MV[], MR>

View file

@ -532,6 +532,12 @@ export class DoublyLinkedList<E = any, R = any> extends IterableElementBase<E, R
this._head = undefined;
this._tail = undefined;
this._size = 0;
if (options) {
const { maxLen } = options;
if (typeof maxLen === 'number' && maxLen > 0 && maxLen % 1 === 0) this._maxLen = maxLen;
}
this.pushMany(elements);
}
@ -566,6 +572,12 @@ export class DoublyLinkedList<E = any, R = any> extends IterableElementBase<E, R
return this._size;
}
protected _maxLen: number = -1;
get maxLen() {
return this._maxLen;
}
/**
* Time Complexity: O(1)
* Space Complexity: O(1)
@ -641,6 +653,7 @@ export class DoublyLinkedList<E = any, R = any> extends IterableElementBase<E, R
this._tail = newNode;
}
this._size++;
if (this._maxLen > 0 && this.size > this._maxLen) this.shift();
return true;
}
@ -707,6 +720,7 @@ export class DoublyLinkedList<E = any, R = any> extends IterableElementBase<E, R
this._head = newNode;
}
this._size++;
if (this._maxLen > 0 && this._size > this._maxLen) this.pop();
return true;
}

View file

@ -68,6 +68,12 @@ export class SinglyLinkedList<E = any, R = any> extends IterableElementBase<E, R
options?: SinglyLinkedListOptions<E, R>
) {
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<E = any, R = any> extends IterableElementBase<E, R
return this.tail?.value;
}
protected _maxLen: number = -1;
get maxLen() {
return this._maxLen;
}
protected _size: number = 0;
/**
@ -155,6 +167,7 @@ export class SinglyLinkedList<E = any, R = any> extends IterableElementBase<E, R
this._tail = newNode;
}
this._size++;
if (this._maxLen > 0 && this.size > this._maxLen) this.shift();
return true;
}

View file

@ -21,8 +21,9 @@ export class Queue<E = any, R = any> extends IterableElementBase<E, R, Queue<E,
super(options);
if (options) {
const { autoCompactRatio = 0.5 } = options;
const { autoCompactRatio = 0.5, maxLen } = options;
this._autoCompactRatio = autoCompactRatio;
if (typeof maxLen === 'number' && maxLen > 0 && maxLen % 1 === 0) this._maxLen = maxLen;
}
this.pushMany(elements);
@ -30,14 +31,16 @@ export class Queue<E = any, R = any> extends IterableElementBase<E, R, Queue<E,
protected _elements: E[] = [];
/**
* The elements function returns the elements of this set.
* @return An array of the elements in the stack
*/
get elements(): E[] {
return this._elements;
}
protected _maxLen: number = -1;
get maxLen() {
return this._maxLen;
}
protected _offset: number = 0;
/**
@ -123,6 +126,7 @@ export class Queue<E = any, R = any> extends IterableElementBase<E, R, Queue<E,
*/
push(element: E): boolean {
this.elements.push(element);
if (this._maxLen > 0 && this.size > this._maxLen) this.shift();
return true;
}

View file

@ -1,3 +1,5 @@
import { IterableElementBaseOptions } from '../base';
export type DoublyLinkedListOptions<E, R> = IterableElementBaseOptions<E, R> & {};
export type DoublyLinkedListOptions<E, R> = IterableElementBaseOptions<E, R> & {
maxLen?: number;
};

View file

@ -1,3 +1,5 @@
import { IterableElementBaseOptions } from '../base';
export type SinglyLinkedListOptions<E, R> = IterableElementBaseOptions<E, R> & {};
export type SinglyLinkedListOptions<E, R> = IterableElementBaseOptions<E, R> & {
maxLen?: number;
};

View file

@ -2,4 +2,5 @@ import { IterableElementBaseOptions } from '../base';
export type QueueOptions<E, R> = IterableElementBaseOptions<E, R> & {
autoCompactRatio?: number;
maxLen?: number;
};

View file

@ -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<number>([], { 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);

View file

@ -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<number>([], { 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', () => {

View file

@ -722,3 +722,58 @@ describe('Deque', () => {
]); // TODO may be a problem
});
});
describe('classic uses', () => {
it('@example prize roulette', () => {
class PrizeRoulette {
private deque: Deque<string>;
constructor(prizes: string[]) {
// Initialize the deque with prizes
this.deque = new Deque<string>(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
});
});

View file

@ -90,6 +90,16 @@ describe('Queue', () => {
expect(queue.isEmpty()).toBeTruthy();
});
it('push with maxLen', () => {
const queue = new Queue<number>([], { 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);