mirror of
https://github.com/zrwusa/data-structure-typed.git
synced 2025-01-18 11:14:05 +00:00
feat: Add maxLen configuration option to all queue-based data structures.
docs: Add example code for Deque.
This commit is contained in:
parent
670225e775
commit
d27784228b
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -2,4 +2,5 @@ import { IterableElementBaseOptions } from '../base';
|
|||
|
||||
export type QueueOptions<E, R> = IterableElementBaseOptions<E, R> & {
|
||||
autoCompactRatio?: number;
|
||||
maxLen?: number;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Loading…
Reference in a new issue