From aab0c87da0c8358e806b49663e273441b374829e Mon Sep 17 00:00:00 2001 From: Revone Date: Fri, 22 Nov 2024 19:53:32 +1300 Subject: [PATCH] docs: add example code for Trie --- src/data-structures/binary-tree/bst.ts | 14 +- src/data-structures/trie/trie.ts | 133 +++++++++++++++-- test/unit/data-structures/trie/trie.test.ts | 151 ++++++++++++++++++++ 3 files changed, 278 insertions(+), 20 deletions(-) diff --git a/src/data-structures/binary-tree/bst.ts b/src/data-structures/binary-tree/bst.ts index 31faccc..6ae50d7 100644 --- a/src/data-structures/binary-tree/bst.ts +++ b/src/data-structures/binary-tree/bst.ts @@ -112,15 +112,11 @@ export class BSTNode = BSTNod * @example * // Find elements in a range * const bst = new BST([10, 5, 15, 3, 7, 12, 18]); - * - * // Helper function to find elements in range - * const findElementsInRange = (min: number, max: number): number[] => { - * return bst.search(node => node.key >= min && node.key <= max, false, node => node.key); - * }; - * - * // Assertions - * console.log(findElementsInRange(4, 12)); // [10, 5, 7, 12] - * console.log(findElementsInRange(15, 20)); // [15, 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.search(new Range(4, 12, true, false))); // [10, 5, 7] + * console.log(bst.search(new Range(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]); diff --git a/src/data-structures/trie/trie.ts b/src/data-structures/trie/trie.ts index 96532e5..5dd45d5 100644 --- a/src/data-structures/trie/trie.ts +++ b/src/data-structures/trie/trie.ts @@ -92,13 +92,106 @@ export class TrieNode { * 9. Spell Check: Checking the spelling of words. * 10. IP Routing: Used in certain types of IP routing algorithms. * 11. Text Word Frequency Count: Counting and storing the frequency of words in a large amount of text data. + * @example + * // Autocomplete: Prefix validation and checking + * const autocomplete = new Trie([ + * 'gmail.com', + * 'gmail.co.nz', + * 'gmail.co.jp', + * 'yahoo.com', + * 'outlook.com' + * ]); + * + * // Get all completions for a prefix + * const gmailCompletions = autocomplete.getWords('gmail'); + * console.log(gmailCompletions); // ['gmail.com','gmail.co.nz','gmail.co.jp'] + * @example + * // File System Path Operations + * const fileSystem = new Trie([ + * '/home/user/documents/file1.txt', + * '/home/user/documents/file2.txt', + * '/home/user/pictures/photo.jpg', + * '/home/user/pictures/vacation/', + * '/home/user/downloads' + * ]); + * + * // Find common directory prefix + * console.log(fileSystem.getLongestCommonPrefix()); // '/home/user/' + * + * // List all files in a directory + * const documentsFiles = fileSystem.getWords('/home/user/documents/'); + * console.log(documentsFiles); // ['/home/user/documents/file1.txt', '/home/user/documents/file2.txt'] + * @example + * // Autocomplete: Basic word suggestions + * // Create a trie for autocomplete + * const autocomplete = new Trie([ + * 'function', + * 'functional', + * 'functions', + * 'class', + * 'classes', + * 'classical', + * 'closure', + * 'const', + * 'constructor' + * ]); + * + * // Test autocomplete with different prefixes + * console.log(autocomplete.getWords('fun')); // ['functional', 'functions','function'] + * console.log(autocomplete.getWords('cla')); // ['classes', 'classical','class', ] + * console.log(autocomplete.getWords('con')); // ['constructor', 'const'] + * + * // Test with non-matching prefix + * console.log(autocomplete.getWords('xyz')); // [] + * @example + * // Dictionary: Case-insensitive word lookup + * // Create a case-insensitive dictionary + * const dictionary = new Trie([], { caseSensitive: false }); + * + * // Add words with mixed casing + * dictionary.add('Hello'); + * dictionary.add('WORLD'); + * dictionary.add('JavaScript'); + * + * // Test lookups with different casings + * console.log(dictionary.has('hello')); // true + * console.log(dictionary.has('HELLO')); // true + * console.log(dictionary.has('Hello')); // true + * console.log(dictionary.has('javascript')); // true + * console.log(dictionary.has('JAVASCRIPT')); // true + * @example + * // IP Address Routing Table + * // Add IP address prefixes and their corresponding routes + * const routes = { + * '192.168.1': 'LAN_SUBNET_1', + * '192.168.2': 'LAN_SUBNET_2', + * '10.0.0': 'PRIVATE_NETWORK_1', + * '10.0.1': 'PRIVATE_NETWORK_2' + * }; + * + * const ipRoutingTable = new Trie(Object.keys(routes)); + * + * // Check IP address prefix matching + * console.log(ipRoutingTable.hasPrefix('192.168.1')); // true + * console.log(ipRoutingTable.hasPrefix('192.168.2')); // true + * + * // Validate IP address belongs to subnet + * const ip = '192.168.1.100'; + * const subnet = ip.split('.').slice(0, 3).join('.'); + * console.log(ipRoutingTable.hasPrefix(subnet)); // true */ export class Trie extends IterableElementBase> { /** - * The constructor function for the Trie class. - * @param words: Iterable string Initialize the trie with a set of words - * @param options?: TrieOptions Allow the user to pass in options for the trie - * @return This + * The constructor initializes a Trie data structure with optional options and words provided as + * input. + * @param {Iterable | Iterable} words - The `words` parameter in the constructor is an + * iterable containing either strings or elements of type `R`. It is used to initialize the Trie with + * a list of words or elements. If no `words` are provided, an empty iterable is used as the default + * value. + * @param [options] - The `options` parameter in the constructor is an optional object that can + * contain configuration options for the Trie data structure. One of the options it can have is + * `caseSensitive`, which is a boolean value indicating whether the Trie should be case-sensitive or + * not. If `caseSensitive` is set to ` */ constructor(words: Iterable | Iterable = [], options?: TrieOptions) { super(options); @@ -107,13 +200,7 @@ export class Trie extends IterableElementBase> { if (caseSensitive !== undefined) this._caseSensitive = caseSensitive; } if (words) { - for (const word of words) { - if (this.toElementFn) { - this.add(this.toElementFn(word as R)); - } else { - this.add(word as string); - } - } + this.addMany(words); } } @@ -175,6 +262,30 @@ export class Trie extends IterableElementBase> { return isNewWord; } + /** + * Time Complexity: O(n * l) + * Space Complexity: O(1) + * + * The `addMany` function in TypeScript takes an iterable of strings or elements of type R, converts + * them using a provided function if available, and adds them to a data structure while returning an + * array of boolean values indicating success. + * @param {Iterable | Iterable} words - The `words` parameter in the `addMany` function is + * an iterable that contains either strings or elements of type `R`. + * @returns The `addMany` method returns an array of boolean values indicating whether each word in + * the input iterable was successfully added to the data structure. + */ + addMany(words: Iterable | Iterable = []): boolean[] { + const ans: boolean[] = []; + for (const word of words) { + if (this.toElementFn) { + ans.push(this.add(this.toElementFn(word as R))); + } else { + ans.push(this.add(word as string)); + } + } + return ans; + } + /** * Time Complexity: O(l), where l is the length of the input word. * Space Complexity: O(1) - Constant space. diff --git a/test/unit/data-structures/trie/trie.test.ts b/test/unit/data-structures/trie/trie.test.ts index ea93a5f..072da09 100644 --- a/test/unit/data-structures/trie/trie.test.ts +++ b/test/unit/data-structures/trie/trie.test.ts @@ -932,3 +932,154 @@ describe('Trie class', () => { expect(trieB.hasPrefix('ap')).toBe(false); }); }); + +describe('Trie basic', () => { + test('Dictionary: Basic word lookup functionality', () => { + // Initialize a new Trie and add dictionary words + const dictionary = new Trie(); + const words = ['apple', 'app', 'application', 'approve', 'bread', 'break']; + words.forEach(word => dictionary.add(word)); + + // Test exact word matches + expect(dictionary.has('apple')).toBe(true); + expect(dictionary.has('app')).toBe(true); + expect(dictionary.has('bread')).toBe(true); + + // Test non-existent words + expect(dictionary.has('appl')).toBe(false); + expect(dictionary.has('breaking')).toBe(false); + + // Verify dictionary size + expect(dictionary.size).toBe(words.length); + }); + + test('Autocomplete: Limited suggestions with max results', () => { + const autocomplete = new Trie(); + + // Add city names + const cities = ['New York', 'New Orleans', 'New Delhi', 'New Jersey', 'New Mexico', 'New Hampshire']; + + cities.forEach(city => autocomplete.add(city)); + + // Get limited number of suggestions + const maxSuggestions = 3; + const suggestions = autocomplete.getWords('New', maxSuggestions); + + expect(suggestions.length).toBe(maxSuggestions); + suggestions.forEach(suggestion => { + expect(suggestion.startsWith('New')).toBe(true); + }); + }); + + test('Dictionary: Word removal and updates', () => { + const dictionary = new Trie(); + + // Add initial words + dictionary.add('delete'); + dictionary.add('deletion'); + dictionary.add('deleted'); + + // Verify initial state + expect(dictionary.has('delete')).toBe(true); + expect(dictionary.size).toBe(3); + + // Remove a word + const deleted = dictionary.delete('delete'); + expect(deleted).toBe(true); + expect(dictionary.has('delete')).toBe(false); + expect(dictionary.has('deletion')).toBe(true); + expect(dictionary.has('deleted')).toBe(true); + expect(dictionary.size).toBe(2); + + // Try to remove non-existent word + expect(dictionary.delete('notexist')).toBe(false); + }); +}); + +describe('classic use', () => { + test('@example Autocomplete: Prefix validation and checking', () => { + const autocomplete = new Trie(['gmail.com', 'gmail.co.nz', 'gmail.co.jp', 'yahoo.com', 'outlook.com']); + + // Get all completions for a prefix + const gmailCompletions = autocomplete.getWords('gmail'); + expect(gmailCompletions).toEqual(['gmail.com', 'gmail.co.nz', 'gmail.co.jp']); + }); + + test('@example File System Path Operations', () => { + const fileSystem = new Trie([ + '/home/user/documents/file1.txt', + '/home/user/documents/file2.txt', + '/home/user/pictures/photo.jpg', + '/home/user/pictures/vacation/', + '/home/user/downloads' + ]); + + // Find common directory prefix + expect(fileSystem.getLongestCommonPrefix()).toBe('/home/user/'); + + // List all files in a directory + const documentsFiles = fileSystem.getWords('/home/user/documents/'); + expect(documentsFiles).toEqual(['/home/user/documents/file1.txt', '/home/user/documents/file2.txt']); + }); + + test('@example Autocomplete: Basic word suggestions', () => { + // Create a trie for autocomplete + const autocomplete = new Trie([ + 'function', + 'functional', + 'functions', + 'class', + 'classes', + 'classical', + 'closure', + 'const', + 'constructor' + ]); + + // Test autocomplete with different prefixes + expect(autocomplete.getWords('fun')).toEqual(['functional', 'functions', 'function']); + expect(autocomplete.getWords('cla')).toEqual(['classes', 'classical', 'class']); + expect(autocomplete.getWords('con')).toEqual(['constructor', 'const']); + + // Test with non-matching prefix + expect(autocomplete.getWords('xyz')).toEqual([]); + }); + + test('@example Dictionary: Case-insensitive word lookup', () => { + // Create a case-insensitive dictionary + const dictionary = new Trie([], { caseSensitive: false }); + + // Add words with mixed casing + dictionary.add('Hello'); + dictionary.add('WORLD'); + dictionary.add('JavaScript'); + + // Test lookups with different casings + expect(dictionary.has('hello')).toBe(true); + expect(dictionary.has('HELLO')).toBe(true); + expect(dictionary.has('Hello')).toBe(true); + expect(dictionary.has('javascript')).toBe(true); + expect(dictionary.has('JAVASCRIPT')).toBe(true); + }); + + test('@example IP Address Routing Table', () => { + // Add IP address prefixes and their corresponding routes + const routes = { + '192.168.1': 'LAN_SUBNET_1', + '192.168.2': 'LAN_SUBNET_2', + '10.0.0': 'PRIVATE_NETWORK_1', + '10.0.1': 'PRIVATE_NETWORK_2' + }; + + const ipRoutingTable = new Trie(Object.keys(routes)); + + // Check IP address prefix matching + expect(ipRoutingTable.hasPrefix('192.168.1')).toBe(true); + expect(ipRoutingTable.hasPrefix('192.168.2')).toBe(true); + + // Validate IP address belongs to subnet + const ip = '192.168.1.100'; + const subnet = ip.split('.').slice(0, 3).join('.'); + expect(ipRoutingTable.hasPrefix(subnet)).toBe(true); + }); +});