From 1494ffa84f4e29340002d0d371eca6daab04ddda Mon Sep 17 00:00:00 2001 From: Revone Date: Sun, 24 Nov 2024 17:37:33 +1300 Subject: [PATCH] docs: more example code for RedBlackTree and BinarySearchTree --- package.json | 2 +- src/data-structures/binary-tree/bst.ts | 58 +++++---- src/data-structures/binary-tree/index.ts | 2 +- .../{rb-tree.ts => red-black-tree.ts} | 52 ++++++++ .../binary-tree/tree-multi-map.ts | 2 +- .../data-structures/binary-tree/bst.test.ts | 73 +++++------ ...rb-tree.test.ts => red-black-tree.test.ts} | 121 ++++++------------ 7 files changed, 154 insertions(+), 156 deletions(-) rename src/data-structures/binary-tree/{rb-tree.ts => red-black-tree.ts} (90%) rename test/unit/data-structures/binary-tree/{rb-tree.test.ts => red-black-tree.test.ts} (90%) diff --git a/package.json b/package.json index 8455bf6..360b14d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "data-structure-typed", - "version": "1.53.7", + "version": "1.53.8", "description": "Javascript Data Structure. Heap, Binary Tree, Red Black Tree, Linked List, Deque, Trie, HashMap, Directed Graph, Undirected Graph, Binary Search Tree(BST), AVL Tree, Priority Queue, Graph, Queue, Tree Multiset, Singly Linked List, Doubly Linked List, Max Heap, Max Priority Queue, Min Heap, Min Priority Queue, Stack. Benchmark compared with C++ STL. API aligned with ES6 and Java.util. Usability is comparable to Python", "main": "dist/cjs/index.js", "module": "dist/mjs/index.js", diff --git a/src/data-structures/binary-tree/bst.ts b/src/data-structures/binary-tree/bst.ts index d9a52e9..a7288f3 100644 --- a/src/data-structures/binary-tree/bst.ts +++ b/src/data-structures/binary-tree/bst.ts @@ -95,32 +95,48 @@ export class BSTNode = BSTNod * 6. Balance Variability: Can become unbalanced; special types maintain balance. * 7. No Auto-Balancing: Standard BSTs don't automatically balance themselves. * @example - * // Find kth smallest element - * // Create a BST with some elements - * const bst = new BST([5, 3, 7, 1, 4, 6, 8]); - * const sortedKeys = bst.dfs(node => node.key, 'IN'); + * // Merge 3 sorted datasets + * const dataset1 = new BST([ + * [1, 'A'], + * [7, 'G'] + * ]); + * const dataset2 = [ + * [2, 'B'], + * [6, 'F'] + * ]; + * const dataset3 = new BST([ + * [3, 'C'], + * [5, 'E'], + * [4, 'D'] + * ]); * - * // Helper function to find kth smallest - * const findKthSmallest = (k: number): number | undefined => { - * return sortedKeys[k - 1]; - * }; + * // Merge datasets into a single BinarySearchTree + * const merged = new BST(dataset1); + * merged.addMany(dataset2); + * merged.merge(dataset3); * - * // Assertions - * console.log(findKthSmallest(1)); // 1 - * console.log(findKthSmallest(3)); // 4 - * console.log(findKthSmallest(7)); // 8 + * // Verify merged dataset is in sorted order + * console.log([...merged.values()]); // ['A', 'B', 'C', 'D', 'E', 'F', 'G'] * @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.search(new Range(4, 12))); // [10, 12, 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(15, 20))); // [15, 18] + * console.log(bst.rangeSearch([15, 20])); // [15, 18] * console.log(bst.search(new Range(15, 20, false))); // [18] * @example * // Find lowest common ancestor * const bst = new BST([20, 10, 30, 5, 15, 25, 35, 3, 7, 12, 18]); * + * // LCA helper function + * const findLCA = (num1: number, num2: number): number | undefined => { + * const path1 = bst.getPathToRoot(num1); + * const path2 = bst.getPathToRoot(num2); + * // Find the first common ancestor + * return findFirstCommon(path1, path2); + * }; + * * function findFirstCommon(arr1: number[], arr2: number[]): number | undefined { * for (const num of arr1) { * if (arr2.indexOf(num) !== -1) { @@ -130,14 +146,6 @@ export class BSTNode = BSTNod * return undefined; * } * - * // LCA helper function - * const findLCA = (num1: number, num2: number): number | undefined => { - * const path1 = bst.getPathToRoot(num1); - * const path2 = bst.getPathToRoot(num2); - * // Find the first common ancestor - * return findFirstCommon(path1, path2); - * }; - * * // Assertions * console.log(findLCA(3, 10)); // 7 * console.log(findLCA(5, 35)); // 15 @@ -616,9 +624,6 @@ export class BST< * The `rangeSearch` function searches for nodes within a specified range in a binary search tree. * @param {Range | [K, K]} range - The `range` parameter in the `rangeSearch` function can be * either a `Range` object or an array of two elements representing the range boundaries. - * @param [onlyOne=false] - The `onlyOne` parameter is a boolean flag that indicates whether you want - * to stop the search after finding the first matching node within the specified range. If `onlyOne` - * is set to `true`, the search will return as soon as a single matching node is found. If `onlyOne` * @param {C} callback - The `callback` parameter in the `rangeSearch` function is a callback * function that is used to process each node that is found within the specified range during the * search operation. It is of type `NodeCallback`, where `NODE` is the type of nodes in the @@ -635,13 +640,12 @@ export class BST< */ rangeSearch>( range: Range | [K, K], - onlyOne = false, callback: C = this._DEFAULT_NODE_CALLBACK as C, startNode: BTNRep | R = this._root, iterationType: IterationType = this.iterationType ) { const searchRange: Range = range instanceof Range ? range : new Range(range[0], range[1]); - return this.search(searchRange, onlyOne, callback, startNode, iterationType); + return this.search(searchRange, false, callback, startNode, iterationType); } /** diff --git a/src/data-structures/binary-tree/index.ts b/src/data-structures/binary-tree/index.ts index 0d38e6a..4afe9ea 100644 --- a/src/data-structures/binary-tree/index.ts +++ b/src/data-structures/binary-tree/index.ts @@ -3,6 +3,6 @@ export * from './bst'; export * from './binary-indexed-tree'; export * from './segment-tree'; export * from './avl-tree'; -export * from './rb-tree'; +export * from './red-black-tree'; export * from './avl-tree-multi-map'; export * from './tree-multi-map'; diff --git a/src/data-structures/binary-tree/rb-tree.ts b/src/data-structures/binary-tree/red-black-tree.ts similarity index 90% rename from src/data-structures/binary-tree/rb-tree.ts rename to src/data-structures/binary-tree/red-black-tree.ts index a081803..7b96e88 100644 --- a/src/data-structures/binary-tree/rb-tree.ts +++ b/src/data-structures/binary-tree/red-black-tree.ts @@ -54,6 +54,58 @@ export class RedBlackTreeNode< /** * 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 { + * price: number; // Stock price (key for indexing) + * symbol: string; // Stock ticker symbol + * volume: number; // Trade volume + * } + * + * // Simulate stock market data as it might come from an external feed + * const marketStockData: StockRecord[] = [ + * { price: 142.5, symbol: 'AAPL', volume: 1000000 }, + * { price: 335.2, symbol: 'MSFT', volume: 800000 }, + * { price: 3285.04, symbol: 'AMZN', volume: 500000 }, + * { price: 267.98, symbol: 'META', volume: 750000 }, + * { price: 234.57, symbol: 'GOOGL', volume: 900000 } + * ]; + * + * // Extend the stock record type to include metadata for database usage + * type StockTableRecord = StockRecord & { lastUpdated: Date }; + * + * // Create a Red-Black Tree to index stock records by price + * // Simulates a database index with stock price as the key for quick lookups + * const priceIndex = new RedBlackTree( + * marketStockData, + * { + * toEntryFn: stockRecord => [ + * stockRecord.price, // Use stock price as the key + * { + * ...stockRecord, + * lastUpdated: new Date() // Add a timestamp for when the record was indexed + * } + * ] + * } + * ); + * + * // Query the stock with the highest price + * const highestPricedStock = priceIndex.getRightMost(); + * console.log(priceIndex.get(highestPricedStock)?.symbol); // 'AMZN' // Amazon has the highest price + * + * // Query stocks within a specific price range (200 to 400) + * const stocksInRange = priceIndex.rangeSearch( + * [200, 400], // Price range + * node => priceIndex.get(node)?.symbol // Extract stock symbols for the result + * ); + * console.log(stocksInRange); // ['GOOGL', 'MSFT', 'META'] */ export class RedBlackTree< K = any, diff --git a/src/data-structures/binary-tree/tree-multi-map.ts b/src/data-structures/binary-tree/tree-multi-map.ts index 1f41644..77d1865 100644 --- a/src/data-structures/binary-tree/tree-multi-map.ts +++ b/src/data-structures/binary-tree/tree-multi-map.ts @@ -17,7 +17,7 @@ import type { TreeMultiMapOptions } from '../../types'; import { IBinaryTree } from '../../interfaces'; -import { RedBlackTree, RedBlackTreeNode } from './rb-tree'; +import { RedBlackTree, RedBlackTreeNode } from './red-black-tree'; export class TreeMultiMapNode< K = any, diff --git a/test/unit/data-structures/binary-tree/bst.test.ts b/test/unit/data-structures/binary-tree/bst.test.ts index ef42bde..0f72ef1 100644 --- a/test/unit/data-structures/binary-tree/bst.test.ts +++ b/test/unit/data-structures/binary-tree/bst.test.ts @@ -58,7 +58,7 @@ describe('BST operations test', () => { [10, 10], [5, 5] ]; - bst.addMany(idsAndValues, undefined, false); + bst.addMany(idsAndValues, [], false); expect(bst.root).toBeInstanceOf(BSTNode); if (bst.root) expect(bst.root.key).toBe(11); @@ -1538,57 +1538,52 @@ describe('BST iterative methods not map mode test', () => { }); describe('classic use', () => { - // Test case for finding the kth smallest element - it('@example Find kth smallest element', () => { - // Create a BST with some elements - const bst = new BST([5, 3, 7, 1, 4, 6, 8]); - const sortedKeys = bst.dfs(node => node.key, 'IN'); + it('@example Merge 3 sorted datasets', () => { + const dataset1 = new BST([ + [1, 'A'], + [7, 'G'] + ]); + const dataset2 = [ + [2, 'B'], + [6, 'F'] + ]; + const dataset3 = new BST([ + [3, 'C'], + [5, 'E'], + [4, 'D'] + ]); - // Helper function to find kth smallest - const findKthSmallest = (k: number): number | undefined => { - return sortedKeys[k - 1]; - }; + // Merge datasets into a single BinarySearchTree + const merged = new BST(dataset1); + merged.addMany(dataset2); + merged.merge(dataset3); - // Assertions - expect(findKthSmallest(1)).toBe(1); - expect(findKthSmallest(3)).toBe(4); - expect(findKthSmallest(7)).toBe(8); + // Verify merged dataset is in sorted order + expect([...merged.values()]).toEqual(['A', 'B', 'C', 'D', 'E', 'F', 'G']); }); // Test case for finding elements in a given range it('@example Find elements in a range', () => { const bst = new BST([10, 5, 15, 3, 7, 12, 18]); expect(bst.search(new Range(5, 10))).toEqual([10, 5, 7]); - expect(bst.rangeSearch([4, 12], false, node => node.key.toString())).toEqual(['10', '12', '5', '7']); + expect(bst.rangeSearch([4, 12], node => node.key.toString())).toEqual(['10', '12', '5', '7']); expect(bst.search(new Range(4, 12, true, false))).toEqual([10, 5, 7]); expect(bst.rangeSearch([15, 20])).toEqual([15, 18]); expect(bst.search(new Range(15, 20, false))).toEqual([18]); }); - // Test case for Huffman coding simulation - it('Huffman coding frequency simulation', () => { - // Create a BST to simulate Huffman tree - const frequencyBST = new BST([ - ['a', 5], - ['b', 9], - ['c', 12], - ['d', 13], - ['e', 16], - ['f', 45] - ]); - - // Sort nodes by frequency - const sortedFrequencies = frequencyBST.dfs(node => ({ char: node.key, freq: node.value }), 'IN'); - - // Build Huffman tree simulation - expect(sortedFrequencies[0].char).toBe('a'); - expect(sortedFrequencies[5].char).toBe('f'); - }); - // Test case for Lowest Common Ancestor (LCA) it('@example Find lowest common ancestor', () => { const bst = new BST([20, 10, 30, 5, 15, 25, 35, 3, 7, 12, 18]); + // LCA helper function + const findLCA = (num1: number, num2: number): number | undefined => { + const path1 = bst.getPathToRoot(num1); + const path2 = bst.getPathToRoot(num2); + // Find the first common ancestor + return findFirstCommon(path1, path2); + }; + function findFirstCommon(arr1: number[], arr2: number[]): number | undefined { for (const num of arr1) { if (arr2.indexOf(num) !== -1) { @@ -1598,14 +1593,6 @@ describe('classic use', () => { return undefined; } - // LCA helper function - const findLCA = (num1: number, num2: number): number | undefined => { - const path1 = bst.getPathToRoot(num1); - const path2 = bst.getPathToRoot(num2); - // Find the first common ancestor - return findFirstCommon(path1, path2); - }; - // Assertions expect(findLCA(3, 10)).toBe(7); expect(findLCA(5, 35)).toBe(15); diff --git a/test/unit/data-structures/binary-tree/rb-tree.test.ts b/test/unit/data-structures/binary-tree/red-black-tree.test.ts similarity index 90% rename from test/unit/data-structures/binary-tree/rb-tree.test.ts rename to test/unit/data-structures/binary-tree/red-black-tree.test.ts index f2a7796..2ace962 100644 --- a/test/unit/data-structures/binary-tree/rb-tree.test.ts +++ b/test/unit/data-structures/binary-tree/red-black-tree.test.ts @@ -821,100 +821,55 @@ describe('RedBlackTree - _deleteFixup', () => { }); describe('classic use', () => { - it('Database Index: Add, Search, and Delete Records', () => { - const dbIndex = new RedBlackTree(); - - // Insert records - dbIndex.add(1, 'Alice'); - dbIndex.add(2, 'Bob'); - dbIndex.add(3, 'Charlie'); - - // Search for records - expect(dbIndex.get(1)).toBe('Alice'); - expect(dbIndex.get(2)).toBe('Bob'); - expect(dbIndex.get(3)).toBe('Charlie'); - - // Delete a record - dbIndex.delete(2); - expect(dbIndex.get(2)).toBeUndefined(); - }); - - it('@example Merge 3 sorted datasets', () => { - const dataset1 = new RedBlackTree([ - [1, 'A'], - [7, 'G'] - ]); - const dataset2 = [ - [2, 'B'], - [6, 'F'] - ]; - const dataset3 = new RedBlackTree([ - [3, 'C'], - [5, 'E'], - [4, 'D'] - ]); - - // Merge datasets into a single Red-Black Tree - const merged = new RedBlackTree(dataset1); - merged.addMany(dataset2); - merged.merge(dataset3); - - // Verify merged dataset is in sorted order - expect([...merged.values()]).toEqual(['A', 'B', 'C', 'D', 'E', 'F', 'G']); - }); - // Test case for finding elements in a given range - it('Find elements in a range', () => { + it('@example Find elements in a range', () => { const bst = new RedBlackTree([10, 5, 15, 3, 7, 12, 18]); expect(bst.search(new Range(5, 10))).toEqual([5, 10, 7]); expect(bst.search(new Range(4, 12))).toEqual([5, 10, 12, 7]); expect(bst.search(new Range(15, 20))).toEqual([15, 18]); }); - it('Timer List: Manage Timed Tasks', () => { - const timerList = new RedBlackTree(); // Key: Time in ms, Value: Task Name + it('@example using Red-Black Tree as a price-based index for stock data', () => { + // Define the structure of individual stock records + interface StockRecord { + price: number; // Stock price (key for indexing) + symbol: string; // Stock ticker symbol + volume: number; // Trade volume + } - // Schedule tasks - timerList.add(100, 'Task A'); - timerList.add(200, 'Task B'); - timerList.add(50, 'Task C'); + // Simulate stock market data as it might come from an external feed + const marketStockData: StockRecord[] = [ + { price: 142.5, symbol: 'AAPL', volume: 1000000 }, + { price: 335.2, symbol: 'MSFT', volume: 800000 }, + { price: 3285.04, symbol: 'AMZN', volume: 500000 }, + { price: 267.98, symbol: 'META', volume: 750000 }, + { price: 234.57, symbol: 'GOOGL', volume: 900000 } + ]; - // Verify the order of tasks by retrieval - expect([...timerList.values()]).toEqual(['Task C', 'Task A', 'Task B']); // Sorted by key (time) + // Extend the stock record type to include metadata for database usage + type StockTableRecord = StockRecord & { lastUpdated: Date }; - // Remove the earliest task - timerList.delete(50); - expect([...timerList.values()]).toEqual(['Task A', 'Task B']); - }); + // Create a Red-Black Tree to index stock records by price + // Simulates a database index with stock price as the key for quick lookups + const priceIndex = new RedBlackTree(marketStockData, { + toEntryFn: stockRecord => [ + stockRecord.price, // Use stock price as the key + { + ...stockRecord, + lastUpdated: new Date() // Add a timestamp for when the record was indexed + } + ] + }); - it('Scheduler: Manage Tasks by Priority', () => { - const scheduler = new RedBlackTree(); // Key: Priority, Value: Task Name + // Query the stock with the highest price + const highestPricedStock = priceIndex.getRightMost(); + expect(priceIndex.get(highestPricedStock)?.symbol).toBe('AMZN'); // Amazon has the highest price - // Add tasks with different priorities - scheduler.add(3, 'Low Priority Task'); - scheduler.add(1, 'High Priority Task'); - scheduler.add(2, 'Medium Priority Task'); - - // Verify the order of tasks by retrieval - expect([...scheduler.values()]).toEqual(['High Priority Task', 'Medium Priority Task', 'Low Priority Task']); - - // Remove the highest priority task - scheduler.delete(1); - expect([...scheduler.values()]).toEqual(['Medium Priority Task', 'Low Priority Task']); - }); - - it('Routing Table: Manage IP Routes', () => { - const routingTable = new RedBlackTree(); // Key: IP Address, Value: Route - - // Add routes - routingTable.add(1921680101, 'Route A'); - routingTable.add(1921680102, 'Route B'); - routingTable.add(1921680100, 'Route C'); - - // Search for a specific route - expect(routingTable.get(1921680101)).toBe('Route A'); - - // Verify all routes in sorted order - expect([...routingTable.values()]).toEqual(['Route C', 'Route A', 'Route B']); + // Query stocks within a specific price range (200 to 400) + const stocksInRange = priceIndex.rangeSearch( + [200, 400], // Price range + node => priceIndex.get(node)?.symbol // Extract stock symbols for the result + ); + expect(stocksInRange).toEqual(['GOOGL', 'MSFT', 'META']); // Verify stocks in the range }); });