From 670225e7750f7808b08e3480e18a8394cd00f5fb Mon Sep 17 00:00:00 2001 From: Revone Date: Tue, 3 Dec 2024 11:47:32 +1300 Subject: [PATCH] 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. --- CHANGELOG.md | 2 +- package-lock.json | 44 ++--- package.json | 8 +- scripts/testToExample.ts | 10 ++ .../binary-tree/avl-tree-multi-map.ts | 89 +++++---- src/data-structures/binary-tree/avl-tree.ts | 63 +++++++ .../binary-tree/binary-tree.ts | 62 +++++++ src/data-structures/binary-tree/bst.ts | 22 +-- .../binary-tree/red-black-tree.ts | 8 +- .../binary-tree/tree-multi-map.ts | 92 ++++++---- .../binary-tree/avl-tree-multi-map.ts | 2 +- .../binary-tree/tree-multi-map.ts | 2 +- .../binary-tree/avl-tree-multi-map.test.ts | 168 +++++++++++++++++ .../binary-tree/avl-tree.test.ts | 27 +-- .../binary-tree/tree-multi-map.test.ts | 170 +++++++++++++++++- 15 files changed, 628 insertions(+), 141 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc1be59..921159b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/package-lock.json b/package-lock.json index a5ddd90..750f400 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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": { diff --git a/package.json b/package.json index afe4182..f2092af 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/scripts/testToExample.ts b/scripts/testToExample.ts index dbd6576..8c6cf9d 100644 --- a/scripts/testToExample.ts +++ b/scripts/testToExample.ts @@ -45,6 +45,14 @@ const dirMap: Record = { Trie: "trie-typed", UndirectedGraph: "undirected-graph-typed", }; + +const classMap: Record = { + 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]; diff --git a/src/data-structures/binary-tree/avl-tree-multi-map.ts b/src/data-structures/binary-tree/avl-tree-multi-map.ts index d1e819b..8ab01e2 100644 --- a/src/data-structures/binary-tree/avl-tree-multi-map.ts +++ b/src/data-structures/binary-tree/avl-tree-multi-map.ts @@ -100,6 +100,7 @@ export class AVLTreeMultiMap { - return new AVLTreeMultiMapNode(key, []); + override createNode(key: K, value: V[] = []): AVLTreeMultiMapNode { + return new AVLTreeMultiMapNode(key, this._isMapMode ? [] : value); } override add( - node: K | AVLTreeMultiMapNode | [K | null | undefined, V[] | undefined] | null | undefined + keyNodeOrEntry: K | AVLTreeMultiMapNode | [K | null | undefined, V[] | undefined] | null | undefined ): boolean; override add(key: K, value: V): boolean; @@ -129,17 +134,16 @@ export class AVLTreeMultiMap | [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 | null | undefined, V[] | undefined] | null | undefined | K, @@ -150,27 +154,38 @@ export class AVLTreeMultiMap, 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)) { diff --git a/src/data-structures/binary-tree/avl-tree.ts b/src/data-structures/binary-tree/avl-tree.ts index ca73888..ad94187 100644 --- a/src/data-structures/binary-tree/avl-tree.ts +++ b/src/data-structures/binary-tree/avl-tree.ts @@ -60,6 +60,69 @@ export class AVLTreeNode extends BSTNode { * 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(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 extends BST diff --git a/src/data-structures/binary-tree/binary-tree.ts b/src/data-structures/binary-tree/binary-tree.ts index d70a110..84a6e27 100644 --- a/src/data-structures/binary-tree/binary-tree.ts +++ b/src/data-structures/binary-tree/binary-tree.ts @@ -129,6 +129,68 @@ export class BinaryTreeNode { * 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( + * ['stableIncome', 'goodCredit', 'Rejected', 'Approved', 'Rejected'], + * { isDuplicate: true } + * ); + * + * function determineLoanApproval( + * node?: BinaryTreeNode | 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(['+', 3, '*', null, null, 5, '-', null, null, 2, 8]); + * + * function evaluate(node?: BinaryTreeNode | 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 extends IterableEntryBase diff --git a/src/data-structures/binary-tree/bst.ts b/src/data-structures/binary-tree/bst.ts index b896be2..072ff58 100644 --- a/src/data-structures/binary-tree/bst.ts +++ b/src/data-structures/binary-tree/bst.ts @@ -102,9 +102,9 @@ export class BSTNode extends BinaryTreeNode { * @example * // Find elements in a range * const bst = new BST([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 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 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]); } diff --git a/src/data-structures/binary-tree/red-black-tree.ts b/src/data-structures/binary-tree/red-black-tree.ts index 8dcdf00..170fa31 100644 --- a/src/data-structures/binary-tree/red-black-tree.ts +++ b/src/data-structures/binary-tree/red-black-tree.ts @@ -51,12 +51,6 @@ export class RedBlackTreeNode extends BSTNode { * 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([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 extends BSTNode { * [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 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 57df1c6..ed86e5c 100644 --- a/src/data-structures/binary-tree/tree-multi-map.ts +++ b/src/data-structures/binary-tree/tree-multi-map.ts @@ -21,7 +21,7 @@ export class TreeMultiMapNode extends RedBlackTreeNode * @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 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, 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 @@ -82,7 +82,7 @@ export class TreeMultiMap = [], options?: TreeMultiMapOptions ) { - super([], { ...options, isMapMode: true }); + super([], { ...options }); if (keysNodesEntriesOrRaws) { this.addMany(keysNodesEntriesOrRaws); } @@ -107,6 +107,7 @@ export class TreeMultiMap` 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` 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 { - return new TreeMultiMapNode(key, []); + override createNode(key: K, value: V[] = []): TreeMultiMapNode { + return new TreeMultiMapNode(key, this._isMapMode ? [] : value); } - override add(node: K | TreeMultiMapNode | [K | null | undefined, V[] | undefined] | null | undefined): boolean; + override add( + keyNodeOrEntry: K | TreeMultiMapNode | [K | null | undefined, V[] | undefined] | null | undefined + ): boolean; override add(key: K, value: V): boolean; @@ -134,14 +140,13 @@ export class TreeMultiMap | [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, 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)) { diff --git a/src/types/data-structures/binary-tree/avl-tree-multi-map.ts b/src/types/data-structures/binary-tree/avl-tree-multi-map.ts index ad9dfac..267cb37 100644 --- a/src/types/data-structures/binary-tree/avl-tree-multi-map.ts +++ b/src/types/data-structures/binary-tree/avl-tree-multi-map.ts @@ -1,3 +1,3 @@ import type { AVLTreeOptions } from './avl-tree'; -export type AVLTreeMultiMapOptions = Omit, 'isMapMode'> & {} +export type AVLTreeMultiMapOptions = AVLTreeOptions & {} diff --git a/src/types/data-structures/binary-tree/tree-multi-map.ts b/src/types/data-structures/binary-tree/tree-multi-map.ts index 99e3ae2..513eefc 100644 --- a/src/types/data-structures/binary-tree/tree-multi-map.ts +++ b/src/types/data-structures/binary-tree/tree-multi-map.ts @@ -1,3 +1,3 @@ import type { RedBlackTreeOptions } from './red-black-tree'; -export type TreeMultiMapOptions = Omit, 'isMapMode'> & {} +export type TreeMultiMapOptions = RedBlackTreeOptions & {} diff --git a/test/unit/data-structures/binary-tree/avl-tree-multi-map.test.ts b/test/unit/data-structures/binary-tree/avl-tree-multi-map.test.ts index 0e14d84..35e8359 100644 --- a/test/unit/data-structures/binary-tree/avl-tree-multi-map.test.ts +++ b/test/unit/data-structures/binary-tree/avl-tree-multi-map.test.ts @@ -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(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 } + ] + ]); + }); +}); diff --git a/test/unit/data-structures/binary-tree/avl-tree.test.ts b/test/unit/data-structures/binary-tree/avl-tree.test.ts index a04630f..ac3477c 100644 --- a/test/unit/data-structures/binary-tree/avl-tree.test.ts +++ b/test/unit/data-structures/binary-tree/avl-tree.test.ts @@ -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 } + ]); }); }); diff --git a/test/unit/data-structures/binary-tree/tree-multi-map.test.ts b/test/unit/data-structures/binary-tree/tree-multi-map.test.ts index 1638e25..394504c 100644 --- a/test/unit/data-structures/binary-tree/tree-multi-map.test.ts +++ b/test/unit/data-structures/binary-tree/tree-multi-map.test.ts @@ -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([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(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 } + ] + ]); }); });