mirror of
https://github.com/zrwusa/data-structure-typed.git
synced 2025-01-18 11:14:05 +00:00
fix: Fix the issue where an extra array layer was processed for entries during TreeMultiMap instantiation.
feat: Add support for configuration in TreeMultiMap and AVLTreeMultiMap. docs: Add example code for a game leaderboard using TreeMultiMap.
This commit is contained in:
parent
ae42526b91
commit
670225e775
|
@ -8,7 +8,7 @@ All notable changes to this project will be documented in this file.
|
|||
- [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
|
||||
- [`auto-changelog`](https://github.com/CookPete/auto-changelog)
|
||||
|
||||
## [v1.54.2](https://github.com/zrwusa/data-structure-typed/compare/v1.51.5...main) (upcoming)
|
||||
## [v1.54.3](https://github.com/zrwusa/data-structure-typed/compare/v1.51.5...main) (upcoming)
|
||||
|
||||
### Changes
|
||||
|
||||
|
|
44
package-lock.json
generated
44
package-lock.json
generated
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "data-structure-typed",
|
||||
"version": "1.54.2",
|
||||
"version": "1.54.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "data-structure-typed",
|
||||
"version": "1.54.2",
|
||||
"version": "1.54.3",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@eslint/compat": "^1.2.2",
|
||||
|
@ -19,10 +19,10 @@
|
|||
"@typescript-eslint/eslint-plugin": "^8.12.1",
|
||||
"@typescript-eslint/parser": "^8.12.1",
|
||||
"auto-changelog": "^2.5.0",
|
||||
"avl-tree-typed": "^1.54.1",
|
||||
"avl-tree-typed": "^1.54.2",
|
||||
"benchmark": "^2.1.4",
|
||||
"binary-tree-typed": "^1.54.1",
|
||||
"bst-typed": "^1.54.1",
|
||||
"binary-tree-typed": "^1.54.2",
|
||||
"bst-typed": "^1.54.2",
|
||||
"data-structure-typed": "^1.54.2",
|
||||
"dependency-cruiser": "^16.5.0",
|
||||
"doctoc": "^2.2.1",
|
||||
|
@ -32,7 +32,7 @@
|
|||
"eslint-import-resolver-typescript": "^3.6.3",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"fast-glob": "^3.3.2",
|
||||
"heap-typed": "^1.54.1",
|
||||
"heap-typed": "^1.54.2",
|
||||
"istanbul-badges-readme": "^1.9.0",
|
||||
"jest": "^29.7.0",
|
||||
"js-sdsl": "^4.4.2",
|
||||
|
@ -3450,13 +3450,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/avl-tree-typed": {
|
||||
"version": "1.54.1",
|
||||
"resolved": "https://registry.npmjs.org/avl-tree-typed/-/avl-tree-typed-1.54.1.tgz",
|
||||
"integrity": "sha512-PhaVjii3L+vm57mf/AplYZeIFuUoe8AS470ULkiBiLICt5g3O5FlP/B7JMMl8EyzDz3NyA9XcQXW3gwZ7mEL8w==",
|
||||
"version": "1.54.2",
|
||||
"resolved": "https://registry.npmjs.org/avl-tree-typed/-/avl-tree-typed-1.54.2.tgz",
|
||||
"integrity": "sha512-bp+MMq2CpASeAB7up5kggHZhcqxx3sKdd5AJ8aWMqabZin13BxRnzwRY/II9gp4hb4Tvtitw6AYfJliGcdWYyQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"data-structure-typed": "^1.54.1"
|
||||
"data-structure-typed": "^1.54.2"
|
||||
}
|
||||
},
|
||||
"node_modules/babel-jest": {
|
||||
|
@ -3615,13 +3615,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/binary-tree-typed": {
|
||||
"version": "1.54.1",
|
||||
"resolved": "https://registry.npmjs.org/binary-tree-typed/-/binary-tree-typed-1.54.1.tgz",
|
||||
"integrity": "sha512-lYFAKI9bCqA0yrXZYnoUU2szPng1JgyUanGOoqbgIuDncPY3jNnmRZYHUCgwkRi7mu2ukfoUIohP/JSH3g6MTg==",
|
||||
"version": "1.54.2",
|
||||
"resolved": "https://registry.npmjs.org/binary-tree-typed/-/binary-tree-typed-1.54.2.tgz",
|
||||
"integrity": "sha512-U6qW0bIh6o2qGz8OzWzUHnNwXa6HjYp0I5Ds4VURqtl4OEOeJh57cNOlKgbiEHZ5viai7S5aRqXIZHW6vStezA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"data-structure-typed": "^1.54.1"
|
||||
"data-structure-typed": "^1.54.2"
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
|
@ -3705,13 +3705,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/bst-typed": {
|
||||
"version": "1.54.1",
|
||||
"resolved": "https://registry.npmjs.org/bst-typed/-/bst-typed-1.54.1.tgz",
|
||||
"integrity": "sha512-2wfrlfxeVdHMfXOJ8Uw7DOFpzBgw6weaE1u6ZrHdJhvgP1cWanpd3Vasnq8EI5g/NjAjH4hCaWkCRaDPjXyjKw==",
|
||||
"version": "1.54.2",
|
||||
"resolved": "https://registry.npmjs.org/bst-typed/-/bst-typed-1.54.2.tgz",
|
||||
"integrity": "sha512-hl49IvHT/uJMphP1AL6sifiCKVc0pCdsGG8PW5ealU2K0LO9kO3nMBU+FcfwsmMcvhRrhikW/1fC63c9X7Lq+Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"data-structure-typed": "^1.54.1"
|
||||
"data-structure-typed": "^1.54.2"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer-from": {
|
||||
|
@ -5904,13 +5904,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/heap-typed": {
|
||||
"version": "1.54.1",
|
||||
"resolved": "https://registry.npmjs.org/heap-typed/-/heap-typed-1.54.1.tgz",
|
||||
"integrity": "sha512-zIiCZeOC1YSUSxuML63bslyr4PS62DFEA9cEnZRSegppIwKtzsLhmnv4vFCfewbucoHJR+J410zMSsSJH/PW1g==",
|
||||
"version": "1.54.2",
|
||||
"resolved": "https://registry.npmjs.org/heap-typed/-/heap-typed-1.54.2.tgz",
|
||||
"integrity": "sha512-BEgfIZndyoxN4XRY9kWSS8217u6nt4GuWrBWBnNxZwp8EVyhExS/CA3NsOH8xzj3mgsxqU88LdWwGFpQqBxMbA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"data-structure-typed": "^1.54.1"
|
||||
"data-structure-typed": "^1.54.2"
|
||||
}
|
||||
},
|
||||
"node_modules/html-escaper": {
|
||||
|
|
|
@ -65,10 +65,10 @@
|
|||
"@typescript-eslint/eslint-plugin": "^8.12.1",
|
||||
"@typescript-eslint/parser": "^8.12.1",
|
||||
"auto-changelog": "^2.5.0",
|
||||
"avl-tree-typed": "^1.54.1",
|
||||
"avl-tree-typed": "^1.54.2",
|
||||
"benchmark": "^2.1.4",
|
||||
"binary-tree-typed": "^1.54.1",
|
||||
"bst-typed": "^1.54.1",
|
||||
"binary-tree-typed": "^1.54.2",
|
||||
"bst-typed": "^1.54.2",
|
||||
"data-structure-typed": "^1.54.2",
|
||||
"dependency-cruiser": "^16.5.0",
|
||||
"doctoc": "^2.2.1",
|
||||
|
@ -78,7 +78,7 @@
|
|||
"eslint-import-resolver-typescript": "^3.6.3",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"fast-glob": "^3.3.2",
|
||||
"heap-typed": "^1.54.1",
|
||||
"heap-typed": "^1.54.2",
|
||||
"istanbul-badges-readme": "^1.9.0",
|
||||
"jest": "^29.7.0",
|
||||
"js-sdsl": "^4.4.2",
|
||||
|
|
|
@ -45,6 +45,14 @@ const dirMap: Record<string, string | string[]> = {
|
|||
Trie: "trie-typed",
|
||||
UndirectedGraph: "undirected-graph-typed",
|
||||
};
|
||||
|
||||
const classMap: Record<string, string> = {
|
||||
Bst: "BST",
|
||||
AvlTree: "AVLTree",
|
||||
AvlTreeMultiMap: "AVLTreeMultiMap",
|
||||
AvlTreeCounter: "AVLTreeCounter"
|
||||
}
|
||||
|
||||
const fileName = 'README.md';
|
||||
|
||||
/**
|
||||
|
@ -218,6 +226,8 @@ function updateExamples(testDir: string, sourceBaseDir: string): void {
|
|||
const sourceFilePath = path.resolve(sourceBaseDir, relativePath.replace('.test.ts', '.ts'));
|
||||
let className = toPascalCase(path.basename(sourceFilePath, '.ts'));
|
||||
if (className === 'Bst') className = 'BST';
|
||||
if (className === 'AvlTree') className = 'AVLTree';
|
||||
className = classMap[className] || className;
|
||||
addExamplesToSourceFile(sourceFilePath, className, examples);
|
||||
const dirKey = dirMap[className];
|
||||
|
||||
|
|
|
@ -100,6 +100,7 @@ export class AVLTreeMultiMap<K = any, V = any, R = object, MK = any, MV = any, M
|
|||
specifyComparable: this._specifyComparable,
|
||||
toEntryFn: this._toEntryFn,
|
||||
isReverse: this._isReverse,
|
||||
isMapMode: this._isMapMode,
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
@ -108,19 +109,23 @@ export class AVLTreeMultiMap<K = any, V = any, R = object, MK = any, MV = any, M
|
|||
* Time Complexity: O(1)
|
||||
* Space Complexity: O(1)
|
||||
*
|
||||
* The function `createNode` overrides the method to create a new AVLTreeMultiMapNode with a
|
||||
* specified key and an empty array of values.
|
||||
* @param {K} key - The `key` parameter in the `createNode` method represents the key of the node
|
||||
* that will be created in the AVLTreeMultiMap.
|
||||
* @returns An AVLTreeMultiMapNode object is being returned, initialized with the provided key and an
|
||||
* empty array.
|
||||
* The `createNode` function in TypeScript overrides the default implementation to create a new
|
||||
* AVLTreeMultiMapNode with a specified key and value array.
|
||||
* @param {K} key - The `key` parameter represents the key of the node being created in the
|
||||
* AVLTreeMultiMap.
|
||||
* @param {V[]} value - The `value` parameter in the `createNode` method represents an array of
|
||||
* values associated with a specific key in the AVLTreeMultiMapNode. If no value is provided when
|
||||
* calling the method, an empty array `[]` is used as the default value.
|
||||
* @returns An AVLTreeMultiMapNode object is being returned, with the specified key and value. If the
|
||||
* AVLTreeMultiMap is in map mode, an empty array is used as the value, otherwise the provided value
|
||||
* array is used.
|
||||
*/
|
||||
override createNode(key: K): AVLTreeMultiMapNode<K, V> {
|
||||
return new AVLTreeMultiMapNode<K, V>(key, []);
|
||||
override createNode(key: K, value: V[] = []): AVLTreeMultiMapNode<K, V> {
|
||||
return new AVLTreeMultiMapNode<K, V>(key, this._isMapMode ? [] : value);
|
||||
}
|
||||
|
||||
override add(
|
||||
node: K | AVLTreeMultiMapNode<K, V> | [K | null | undefined, V[] | undefined] | null | undefined
|
||||
keyNodeOrEntry: K | AVLTreeMultiMapNode<K, V> | [K | null | undefined, V[] | undefined] | null | undefined
|
||||
): boolean;
|
||||
|
||||
override add(key: K, value: V): boolean;
|
||||
|
@ -129,17 +134,16 @@ export class AVLTreeMultiMap<K = any, V = any, R = object, MK = any, MV = any, M
|
|||
* Time Complexity: O(log n)
|
||||
* Space Complexity: O(log n)
|
||||
*
|
||||
* The function `add` in TypeScript overrides the superclass method to add key-value pairs to an AVL
|
||||
* tree multi-map.
|
||||
* @param {K | AVLTreeMultiMapNode<K, V> | [K | null | undefined, V[] | undefined] | null | undefined | K} keyNodeOrEntry - The `keyNodeOrEntry`
|
||||
* parameter in the `override add` method can be either a key-value pair entry or just a key. If it
|
||||
* is a key-value pair entry, it will be in the format `[key, values]`, where `key` is the key and
|
||||
* `values`
|
||||
* @param {V} [value] - The `value` parameter in the `override add` method represents the value that
|
||||
* you want to add to the AVLTreeMultiMap. It can be a single value or an array of values associated
|
||||
* with a specific key.
|
||||
* @returns The `override add` method is returning a boolean value, which indicates whether the
|
||||
* addition operation was successful or not.
|
||||
* The function `add` in this TypeScript code overrides the superclass method to add key-value pairs
|
||||
* to an AVLTreeMultiMap, handling different input types and scenarios.
|
||||
* @param [key] - The `key` parameter in the `override add` method represents the key of the entry to
|
||||
* be added to the AVLTreeMultiMap. It can be of type `K`, which is the key type of the map. The key
|
||||
* can be a single key value, a node of the AVLTree
|
||||
* @param {V[]} [values] - The `values` parameter in the `add` method represents an array of values
|
||||
* that you want to add to the AVLTreeMultiMap. It can contain one or more values associated with a
|
||||
* specific key.
|
||||
* @returns The `add` method is returning a boolean value, which indicates whether the operation was
|
||||
* successful or not.
|
||||
*/
|
||||
override add(
|
||||
keyNodeOrEntry: K | AVLTreeMultiMapNode<K, V> | [K | null | undefined, V[] | undefined] | null | undefined | K,
|
||||
|
@ -150,27 +154,38 @@ export class AVLTreeMultiMap<K = any, V = any, R = object, MK = any, MV = any, M
|
|||
const _commonAdd = (key?: BTNOptKeyOrNull<K>, values?: V[]) => {
|
||||
if (key === undefined || key === null) return false;
|
||||
|
||||
const existingValues = this.get(key);
|
||||
if (existingValues !== undefined && values !== undefined) {
|
||||
for (const value of values) existingValues.push(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
const existingNode = this.getNode(key);
|
||||
if (this.isRealNode(existingNode)) {
|
||||
if (existingValues === undefined) {
|
||||
super.add(key, values);
|
||||
return true;
|
||||
}
|
||||
if (values !== undefined) {
|
||||
const _addToValues = () => {
|
||||
const existingValues = this.get(key);
|
||||
if (existingValues !== undefined && values !== undefined) {
|
||||
for (const value of values) existingValues.push(value);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return super.add(key, values);
|
||||
return false;
|
||||
};
|
||||
|
||||
const _addByNode = () => {
|
||||
const existingNode = this.getNode(key);
|
||||
if (this.isRealNode(existingNode)) {
|
||||
const existingValues = this.get(existingNode);
|
||||
if (existingValues === undefined) {
|
||||
super.add(key, values);
|
||||
return true;
|
||||
}
|
||||
if (values !== undefined) {
|
||||
for (const value of values) existingValues.push(value);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return super.add(key, values);
|
||||
}
|
||||
};
|
||||
|
||||
if (this._isMapMode) {
|
||||
return _addByNode() || _addToValues();
|
||||
}
|
||||
return _addToValues() || _addByNode();
|
||||
};
|
||||
|
||||
if (this.isEntry(keyNodeOrEntry)) {
|
||||
|
|
|
@ -60,6 +60,69 @@ export class AVLTreeNode<K = any, V = any> extends BSTNode<K, V> {
|
|||
* 5. Efficient Lookups: Offers O(log n) search time, where 'n' is the number of nodes, due to its balanced nature.
|
||||
* 6. Complex Insertions and Deletions: Due to rebalancing, these operations are more complex than in a regular BST.
|
||||
* 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
|
||||
* type Datum = { timestamp: Date; temperature: number };
|
||||
* // Fixed dataset of CPU temperature readings
|
||||
* const cpuData: Datum[] = [
|
||||
* { timestamp: new Date('2024-12-02T00:00:00'), temperature: 55.1 },
|
||||
* { timestamp: new Date('2024-12-02T00:01:00'), temperature: 56.3 },
|
||||
* { timestamp: new Date('2024-12-02T00:02:00'), temperature: 54.8 },
|
||||
* { timestamp: new Date('2024-12-02T00:03:00'), temperature: 57.2 },
|
||||
* { timestamp: new Date('2024-12-02T00:04:00'), temperature: 58.0 },
|
||||
* { timestamp: new Date('2024-12-02T00:05:00'), temperature: 59.4 },
|
||||
* { timestamp: new Date('2024-12-02T00:06:00'), temperature: 60.1 },
|
||||
* { timestamp: new Date('2024-12-02T00:07:00'), temperature: 61.3 },
|
||||
* { timestamp: new Date('2024-12-02T00:08:00'), temperature: 62.0 },
|
||||
* { timestamp: new Date('2024-12-02T00:09:00'), temperature: 63.5 },
|
||||
* { timestamp: new Date('2024-12-02T00:10:00'), temperature: 64.0 },
|
||||
* { timestamp: new Date('2024-12-02T00:11:00'), temperature: 62.8 },
|
||||
* { timestamp: new Date('2024-12-02T00:12:00'), temperature: 61.5 },
|
||||
* { timestamp: new Date('2024-12-02T00:13:00'), temperature: 60.2 },
|
||||
* { timestamp: new Date('2024-12-02T00:14:00'), temperature: 59.8 },
|
||||
* { timestamp: new Date('2024-12-02T00:15:00'), temperature: 58.6 },
|
||||
* { timestamp: new Date('2024-12-02T00:16:00'), temperature: 57.4 },
|
||||
* { timestamp: new Date('2024-12-02T00:17:00'), temperature: 56.2 },
|
||||
* { timestamp: new Date('2024-12-02T00:18:00'), temperature: 55.7 },
|
||||
* { timestamp: new Date('2024-12-02T00:19:00'), temperature: 54.5 },
|
||||
* { timestamp: new Date('2024-12-02T00:20:00'), temperature: 53.2 },
|
||||
* { timestamp: new Date('2024-12-02T00:21:00'), temperature: 52.8 },
|
||||
* { timestamp: new Date('2024-12-02T00:22:00'), temperature: 51.9 },
|
||||
* { timestamp: new Date('2024-12-02T00:23:00'), temperature: 50.5 },
|
||||
* { timestamp: new Date('2024-12-02T00:24:00'), temperature: 49.8 },
|
||||
* { timestamp: new Date('2024-12-02T00:25:00'), temperature: 48.7 },
|
||||
* { timestamp: new Date('2024-12-02T00:26:00'), temperature: 47.5 },
|
||||
* { timestamp: new Date('2024-12-02T00:27:00'), temperature: 46.3 },
|
||||
* { timestamp: new Date('2024-12-02T00:28:00'), temperature: 45.9 },
|
||||
* { timestamp: new Date('2024-12-02T00:29:00'), temperature: 45.0 }
|
||||
* ];
|
||||
*
|
||||
* // Create an AVL tree to store CPU temperature data
|
||||
* const cpuTemperatureTree = new AVLTree<Date, number, Datum>(cpuData, {
|
||||
* toEntryFn: ({ timestamp, temperature }) => [timestamp, temperature]
|
||||
* });
|
||||
*
|
||||
* // Query a specific time range (e.g., from 00:05 to 00:15)
|
||||
* const rangeStart = new Date('2024-12-02T00:05:00');
|
||||
* const rangeEnd = new Date('2024-12-02T00:15:00');
|
||||
* const rangeResults = cpuTemperatureTree.rangeSearch([rangeStart, rangeEnd], node => ({
|
||||
* minute: node ? node.key.getMinutes() : 0,
|
||||
* 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 }
|
||||
* // ]
|
||||
*/
|
||||
export class AVLTree<K = any, V = any, R = object, MK = any, MV = any, MR = object>
|
||||
extends BST<K, V, R, MK, MV, MR>
|
||||
|
|
|
@ -129,6 +129,68 @@ export class BinaryTreeNode<K = any, V = any> {
|
|||
* 3. Depth and Height: Depth is the number of edges from the root to a node; height is the maximum depth in the tree.
|
||||
* 4. Subtrees: Each child of a node forms the root of a subtree.
|
||||
* 5. Leaf Nodes: Nodes without children are leaves.
|
||||
* @example
|
||||
* // determine loan approval using a decision tree
|
||||
* // Decision tree structure
|
||||
* const loanDecisionTree = new BinaryTree<string>(
|
||||
* ['stableIncome', 'goodCredit', 'Rejected', 'Approved', 'Rejected'],
|
||||
* { isDuplicate: true }
|
||||
* );
|
||||
*
|
||||
* function determineLoanApproval(
|
||||
* node?: BinaryTreeNode<string> | null,
|
||||
* conditions?: { [key: string]: boolean }
|
||||
* ): string {
|
||||
* if (!node) throw new Error('Invalid node');
|
||||
*
|
||||
* // If it's a leaf node, return the decision result
|
||||
* if (!node.left && !node.right) return node.key;
|
||||
*
|
||||
* // Check if a valid condition exists for the current node's key
|
||||
* return conditions?.[node.key]
|
||||
* ? determineLoanApproval(node.left, conditions)
|
||||
* : determineLoanApproval(node.right, conditions);
|
||||
* }
|
||||
*
|
||||
* // Test case 1: Stable income and good credit score
|
||||
* console.log(determineLoanApproval(loanDecisionTree.root, { stableIncome: true, goodCredit: true })); // 'Approved'
|
||||
*
|
||||
* // Test case 2: Stable income but poor credit score
|
||||
* console.log(determineLoanApproval(loanDecisionTree.root, { stableIncome: true, goodCredit: false })); // 'Rejected'
|
||||
*
|
||||
* // Test case 3: No stable income
|
||||
* console.log(determineLoanApproval(loanDecisionTree.root, { stableIncome: false, goodCredit: true })); // 'Rejected'
|
||||
*
|
||||
* // Test case 4: No stable income and poor credit score
|
||||
* console.log(determineLoanApproval(loanDecisionTree.root, { stableIncome: false, goodCredit: false })); // 'Rejected'
|
||||
* @example
|
||||
* // evaluate the arithmetic expression represented by the binary tree
|
||||
* const expressionTree = new BinaryTree<number | string>(['+', 3, '*', null, null, 5, '-', null, null, 2, 8]);
|
||||
*
|
||||
* function evaluate(node?: BinaryTreeNode<number | string> | null): number {
|
||||
* if (!node) return 0;
|
||||
*
|
||||
* if (typeof node.key === 'number') return node.key;
|
||||
*
|
||||
* const leftValue = evaluate(node.left); // Evaluate the left subtree
|
||||
* const rightValue = evaluate(node.right); // Evaluate the right subtree
|
||||
*
|
||||
* // Perform the operation based on the current node's operator
|
||||
* switch (node.key) {
|
||||
* case '+':
|
||||
* return leftValue + rightValue;
|
||||
* case '-':
|
||||
* return leftValue - rightValue;
|
||||
* case '*':
|
||||
* return leftValue * rightValue;
|
||||
* case '/':
|
||||
* return rightValue !== 0 ? leftValue / rightValue : 0; // Handle division by zero
|
||||
* default:
|
||||
* throw new Error(`Unsupported operator: ${node.key}`);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* console.log(evaluate(expressionTree.root)); // -27
|
||||
*/
|
||||
export class BinaryTree<K = any, V = any, R = object, MK = any, MV = any, MR = object>
|
||||
extends IterableEntryBase<K, V | undefined>
|
||||
|
|
|
@ -102,9 +102,9 @@ export class BSTNode<K = any, V = any> extends BinaryTreeNode<K, V> {
|
|||
* @example
|
||||
* // Find elements in a range
|
||||
* const bst = new BST<number>([10, 5, 15, 3, 7, 12, 18]);
|
||||
* console.log(bst.search(new Range(5, 10))); // [10, 5, 7]
|
||||
* console.log(bst.rangeSearch([4, 12], node => node.key.toString())); // ['10', '12', '5', '7']
|
||||
* console.log(bst.search(new Range(4, 12, true, false))); // [10, 5, 7]
|
||||
* console.log(bst.search(new Range(5, 10))); // [5, 7, 10]
|
||||
* console.log(bst.rangeSearch([4, 12], node => node.key.toString())); // ['5', '7', '10', '12']
|
||||
* console.log(bst.search(new Range(4, 12, true, false))); // [5, 7, 10]
|
||||
* console.log(bst.rangeSearch([15, 20])); // [15, 18]
|
||||
* console.log(bst.search(new Range(15, 20, false))); // [18]
|
||||
* @example
|
||||
|
@ -441,14 +441,14 @@ export class BST<K = any, V = any, R = object, MK = any, MV = any, MR = object>
|
|||
if (arr.length === 0) return;
|
||||
|
||||
const mid = Math.floor((arr.length - 1) / 2);
|
||||
let { key, value } = arr[mid];
|
||||
const { key, value } = arr[mid];
|
||||
const { orgIndex } = arr[mid];
|
||||
if (this.isRaw(key)) {
|
||||
const entry = this._toEntryFn!(key);
|
||||
key = entry[0];
|
||||
value = entry[1] ?? value;
|
||||
inserted[orgIndex] = this.add(entry);
|
||||
} else {
|
||||
inserted[orgIndex] = this.add(key, value);
|
||||
}
|
||||
inserted[orgIndex] = this.add(key, value);
|
||||
_dfs(arr.slice(0, mid));
|
||||
_dfs(arr.slice(mid + 1));
|
||||
};
|
||||
|
@ -462,14 +462,14 @@ export class BST<K = any, V = any, R = object, MK = any, MV = any, MR = object>
|
|||
const [l, r] = popped;
|
||||
if (l <= r) {
|
||||
const m = l + Math.floor((r - l) / 2);
|
||||
let { key, value } = sorted[m];
|
||||
const { key, value } = sorted[m];
|
||||
const { orgIndex } = sorted[m];
|
||||
if (this.isRaw(key)) {
|
||||
const entry = this._toEntryFn!(key);
|
||||
key = entry[0];
|
||||
value = entry[1] ?? value;
|
||||
inserted[orgIndex] = this.add(entry);
|
||||
} else {
|
||||
inserted[orgIndex] = this.add(key, value);
|
||||
}
|
||||
inserted[orgIndex] = this.add(key, value);
|
||||
stack.push([m + 1, r]);
|
||||
stack.push([l, m - 1]);
|
||||
}
|
||||
|
|
|
@ -51,12 +51,6 @@ export class RedBlackTreeNode<K = any, V = any> extends BSTNode<K, V> {
|
|||
* 1. Efficient self-balancing, but not completely balanced. Compared with AVLTree, the addition and deletion efficiency is high but the query efficiency is slightly lower.
|
||||
* 2. It is BST itself. Compared with Heap which is not completely ordered, RedBlackTree is completely ordered.
|
||||
* @example
|
||||
* // Find elements in a range
|
||||
* const bst = new RedBlackTree<number>([10, 5, 15, 3, 7, 12, 18]);
|
||||
* console.log(bst.search(new Range(5, 10))); // [5, 10, 7]
|
||||
* console.log(bst.search(new Range(4, 12))); // [5, 10, 12, 7]
|
||||
* console.log(bst.search(new Range(15, 20))); // [15, 18]
|
||||
* @example
|
||||
* // using Red-Black Tree as a price-based index for stock data
|
||||
* // Define the structure of individual stock records
|
||||
* interface StockRecord {
|
||||
|
@ -98,7 +92,7 @@ export class RedBlackTreeNode<K = any, V = any> extends BSTNode<K, V> {
|
|||
* [200, 400], // Price range
|
||||
* node => priceIndex.get(node)?.symbol // Extract stock symbols for the result
|
||||
* );
|
||||
* console.log(stocksInRange); // ['GOOGL', 'MSFT', 'META']
|
||||
* console.log(stocksInRange); // ['GOOGL', 'META', 'MSFT']
|
||||
*/
|
||||
export class RedBlackTree<K = any, V = any, R = object, MK = any, MV = any, MR = object>
|
||||
extends BST<K, V, R, MK, MV, MR>
|
||||
|
|
|
@ -21,7 +21,7 @@ export class TreeMultiMapNode<K = any, V = any> extends RedBlackTreeNode<K, V[]>
|
|||
* @param {V[]} value - The `value` parameter in the constructor represents an array of values of
|
||||
* type `V`.
|
||||
*/
|
||||
constructor(key: K, value: V[]) {
|
||||
constructor(key: K, value?: V[]) {
|
||||
super(key, value);
|
||||
}
|
||||
|
||||
|
@ -57,8 +57,8 @@ 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, 10, 7]
|
||||
* console.log(tmm.search(new Range(4, 12))); // [5, 10, 12, 7]
|
||||
* 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]
|
||||
*/
|
||||
export class TreeMultiMap<K = any, V = any, R = object, MK = any, MV = any, MR = object>
|
||||
|
@ -82,7 +82,7 @@ export class TreeMultiMap<K = any, V = any, R = object, MK = any, MV = any, MR =
|
|||
> = [],
|
||||
options?: TreeMultiMapOptions<K, V[], R>
|
||||
) {
|
||||
super([], { ...options, isMapMode: true });
|
||||
super([], { ...options });
|
||||
if (keysNodesEntriesOrRaws) {
|
||||
this.addMany(keysNodesEntriesOrRaws);
|
||||
}
|
||||
|
@ -107,6 +107,7 @@ export class TreeMultiMap<K = any, V = any, R = object, MK = any, MV = any, MR =
|
|||
specifyComparable: this._specifyComparable,
|
||||
toEntryFn: this._toEntryFn,
|
||||
isReverse: this._isReverse,
|
||||
isMapMode: this._isMapMode,
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
@ -115,18 +116,23 @@ export class TreeMultiMap<K = any, V = any, R = object, MK = any, MV = any, MR =
|
|||
* Time Complexity: O(1)
|
||||
* Space Complexity: O(1)
|
||||
*
|
||||
* The function `createNode` overrides the method to create a new `TreeMultiMapNode` with a specified
|
||||
* key and an empty array of values.
|
||||
* @param {K} key - The `key` parameter in the `createNode` method represents the key of the node
|
||||
* that will be created in the TreeMultiMap data structure.
|
||||
* @returns A new instance of `TreeMultiMapNode<K, V>` is being returned, with the specified key and
|
||||
* an empty array as its value.
|
||||
* The function `createNode` overrides the creation of a new TreeMultiMapNode with a specified key
|
||||
* and value array.
|
||||
* @param {K} key - The `key` parameter represents the key of the node being created in the
|
||||
* `TreeMultiMap`.
|
||||
* @param {V[]} value - The `value` parameter in the `createNode` method represents an array of
|
||||
* values associated with a specific key in the TreeMultiMap data structure.
|
||||
* @returns A new instance of `TreeMultiMapNode<K, V>` is being returned with the specified key and
|
||||
* value. If `_isMapMode` is true, an empty array is passed as the value, otherwise the provided
|
||||
* value is used.
|
||||
*/
|
||||
override createNode(key: K): TreeMultiMapNode<K, V> {
|
||||
return new TreeMultiMapNode<K, V>(key, []);
|
||||
override createNode(key: K, value: V[] = []): TreeMultiMapNode<K, V> {
|
||||
return new TreeMultiMapNode<K, V>(key, this._isMapMode ? [] : value);
|
||||
}
|
||||
|
||||
override add(node: K | TreeMultiMapNode<K, V> | [K | null | undefined, V[] | undefined] | null | undefined): boolean;
|
||||
override add(
|
||||
keyNodeOrEntry: K | TreeMultiMapNode<K, V> | [K | null | undefined, V[] | undefined] | null | undefined
|
||||
): boolean;
|
||||
|
||||
override add(key: K, value: V): boolean;
|
||||
|
||||
|
@ -134,14 +140,13 @@ export class TreeMultiMap<K = any, V = any, R = object, MK = any, MV = any, MR =
|
|||
* Time Complexity: O(log n)
|
||||
* Space Complexity: O(log n)
|
||||
*
|
||||
* The function `add` in TypeScript overrides the superclass method to add key-value pairs to a
|
||||
* TreeMultiMapNode, handling different input types and scenarios.
|
||||
* @param {K | TreeMultiMapNode<K, V> | [K | null | undefined, V[] | undefined] | null | undefined} keyNodeOrEntry - The `keyNodeOrEntry`
|
||||
* parameter in the `override add` method can be either a `BTNRep` object containing a key, an array
|
||||
* of values, and a `TreeMultiMapNode`, or just a key.
|
||||
* @param {V} [value] - The `value` parameter in the `override add` method represents the value that
|
||||
* you want to add to the TreeMultiMap. If the key is already present in the map, the new value will
|
||||
* be added to the existing list of values associated with that key. If the key is not present,
|
||||
* The function overrides the add method to handle different types of input for a TreeMultiMap data
|
||||
* structure.
|
||||
* @param [key] - The `key` parameter in the `override add` method represents the key of the entry to
|
||||
* be added to the TreeMultiMap. It can be of type `K`, which is the key type of the TreeMultiMap, or
|
||||
* it can be a TreeMultiMapNode containing the key and its
|
||||
* @param {V[]} [values] - The `values` parameter in the `add` method represents an array of values
|
||||
* that you want to add to the TreeMultiMap. It can contain one or more values of type `V`.
|
||||
* @returns The `add` method is returning a boolean value, which indicates whether the operation was
|
||||
* successful or not.
|
||||
*/
|
||||
|
@ -154,27 +159,38 @@ export class TreeMultiMap<K = any, V = any, R = object, MK = any, MV = any, MR =
|
|||
const _commonAdd = (key?: BTNOptKeyOrNull<K>, values?: V[]) => {
|
||||
if (key === undefined || key === null) return false;
|
||||
|
||||
const existingValues = this.get(key);
|
||||
if (existingValues !== undefined && values !== undefined) {
|
||||
for (const value of values) existingValues.push(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
const existingNode = this.getNode(key);
|
||||
if (this.isRealNode(existingNode)) {
|
||||
if (existingValues === undefined) {
|
||||
super.add(key, values);
|
||||
return true;
|
||||
}
|
||||
if (values !== undefined) {
|
||||
const _addToValues = () => {
|
||||
const existingValues = this.get(key);
|
||||
if (existingValues !== undefined && values !== undefined) {
|
||||
for (const value of values) existingValues.push(value);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return super.add(key, values);
|
||||
return false;
|
||||
};
|
||||
|
||||
const _addByNode = () => {
|
||||
const existingNode = this.getNode(key);
|
||||
if (this.isRealNode(existingNode)) {
|
||||
const existingValues = this.get(existingNode);
|
||||
if (existingValues === undefined) {
|
||||
super.add(key, values);
|
||||
return true;
|
||||
}
|
||||
if (values !== undefined) {
|
||||
for (const value of values) existingValues.push(value);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return super.add(key, values);
|
||||
}
|
||||
};
|
||||
|
||||
if (this._isMapMode) {
|
||||
return _addByNode() || _addToValues();
|
||||
}
|
||||
return _addToValues() || _addByNode();
|
||||
};
|
||||
|
||||
if (this.isEntry(keyNodeOrEntry)) {
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import type { AVLTreeOptions } from './avl-tree';
|
||||
|
||||
export type AVLTreeMultiMapOptions<K, V, R> = Omit<AVLTreeOptions<K, V, R>, 'isMapMode'> & {}
|
||||
export type AVLTreeMultiMapOptions<K, V, R> = AVLTreeOptions<K, V, R> & {}
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import type { RedBlackTreeOptions } from './red-black-tree';
|
||||
|
||||
export type TreeMultiMapOptions<K, V, R> = Omit<RedBlackTreeOptions<K, V, R>, 'isMapMode'> & {}
|
||||
export type TreeMultiMapOptions<K, V, R> = RedBlackTreeOptions<K, V, R> & {}
|
||||
|
|
|
@ -570,3 +570,171 @@ describe('AVLTreeMultiMap iterative methods not map mode', () => {
|
|||
expect(cloned.get(cloned.root?.right?.key)).toEqual(['c']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('classic use', () => {
|
||||
// Test suite for TreeMultiMap with player ranking and equipment
|
||||
it('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 AVLTreeMultiMap<number, Equipment, Player>(players, {
|
||||
toEntryFn: ({ score, equipments }) => [score, equipments],
|
||||
isMapMode: false
|
||||
});
|
||||
|
||||
const topPlayersEquipments = playerRankings.rangeSearch([8900, 10000], node => playerRankings.get(node));
|
||||
expect(topPlayersEquipments).toEqual([
|
||||
[
|
||||
{
|
||||
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 }
|
||||
]
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -546,6 +546,7 @@ describe('AVLTree iterative methods not map mode', () => {
|
|||
describe('classic use', () => {
|
||||
// Test case for finding elements in a given range
|
||||
it('@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[] = [
|
||||
|
@ -594,18 +595,18 @@ describe('classic use', () => {
|
|||
temperature: cpuTemperatureTree.get(node ? node.key : undefined)
|
||||
}));
|
||||
|
||||
expect(rangeResults).toEqual( [
|
||||
{ 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 }
|
||||
]);
|
||||
expect(rangeResults).toEqual([
|
||||
{ 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 }
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -837,11 +837,169 @@ describe('real world data', () => {
|
|||
});
|
||||
|
||||
describe('classic use', () => {
|
||||
// Test case for finding elements in a given range
|
||||
it('@example Find elements in a range', () => {
|
||||
const tmm = new TreeMultiMap<number>([10, 5, 15, 3, 7, 12, 18]);
|
||||
expect(tmm.search(new Range(5, 10))).toEqual([5, 7, 10]);
|
||||
expect(tmm.search(new Range(4, 12))).toEqual([5, 7, 10, 12]);
|
||||
expect(tmm.search(new Range(15, 20))).toEqual([15, 18]);
|
||||
// Test suite for TreeMultiMap with player ranking and equipment
|
||||
it('@example 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));
|
||||
expect(topPlayersEquipments).toEqual([
|
||||
[
|
||||
{
|
||||
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 }
|
||||
]
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue