├── .eslintrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── Gruntfile.js ├── LICENSE ├── README.md ├── index.d.ts ├── index.js ├── package.json ├── src ├── trie.d.ts ├── trie.js ├── trieNode.d.ts └── trieNode.js └── test └── trie.test.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "max-len": ["error", { "code": 80, "ignoreComments": true }], 4 | "comma-dangle": ["error", { 5 | "functions": "ignore" 6 | }], 7 | "no-underscore-dangle": [ 8 | "error", 9 | { "allowAfterThis": true } 10 | ] 11 | }, 12 | "env": { 13 | "mocha": true, 14 | "node": true 15 | }, 16 | "extends": ["airbnb-base"] 17 | } 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | .DS_Store -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .git* 2 | .travis.yml 3 | .gitignore 4 | .eslintrc 5 | Gruntfile.js 6 | coverage/ 7 | node_modules/ 8 | test/ 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8" 4 | - "9" 5 | - "10" 6 | - "11" 7 | - "12" 8 | install: 9 | - npm install -g grunt-cli 10 | - npm install 11 | script: 12 | - grunt build 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | ## [4.2.2] - 2022-08-15 9 | ### Fixed 10 | - add types to package.json 11 | 12 | ## [4.2.1] - 2022-06-06 13 | 14 | ### Fixed 15 | - readme. 16 | 17 | ## [4.2.0] - 2021-12-01 18 | 19 | ### Added 20 | - `isLeaf()` to TrieNode: leaf is a node that has no children. 21 | 22 | ## [4.2.0] - 2021-12-01 23 | 24 | ### Added 25 | - `isLeaf()` to TrieNode: leaf is a node that has no children. 26 | 27 | ### Fixed 28 | - `remove(word)` two edge cases that were not covered: 29 | 1. the case when removing a word that does not exist, count should not change. 30 | 2. the case when another word overlaps with the word being deleted, it was removing all the word chars regardless if one char is an end of another word. 31 | 32 | **Credit:** 王悠悠 https://github.com/anson09 33 | 34 | ## [4.1.1] - 2021-06-20 35 | 36 | ### Fixed 37 | - index.d.ts 38 | 39 | ## [4.1.0] - 2021-06-20 40 | 41 | ### Added 42 | - typescript. 43 | 44 | ## [4.0.1] - 2021-02-25 45 | 46 | ### Fixed 47 | - README 48 | 49 | ## [4.0.0] - 2021-02-23 50 | 51 | ### Changed 52 | - `.insert` can be chained. 53 | - `.remove` now returns the removed word. 54 | - better handling for null & undefined. 55 | 56 | ### Added 57 | - `.fromArray` static function to convert a list into a trie. 58 | 59 | ### Fixed 60 | - jsdoc 61 | - README 62 | 63 | ## [3.0.1] - 2020-04-18 64 | ### Fixed 65 | - jsdoc 66 | - README 67 | 68 | ## [3.0.0] - 2020-04-09 69 | ### Changed 70 | - renamed `.getWordsCount()` & `.getNodesCount()` to `.wordsCount()` & `.nodesCount()`. 71 | 72 | ### Fixed 73 | - README 74 | - jsdoc 75 | 76 | ## [2.0.0] - 2020-03-24 77 | ### Changed 78 | - new implementation and interface 79 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = (grunt) => { 2 | grunt.initConfig({ 3 | eslint: { 4 | src: ['src/*.js', 'test/*.test.js'] 5 | }, 6 | mochaTest: { 7 | files: ['test/*.test.js'] 8 | }, 9 | mocha_istanbul: { 10 | coverage: { 11 | src: 'test', 12 | options: { 13 | mask: '*.test.js' 14 | } 15 | } 16 | } 17 | }); 18 | 19 | grunt.loadNpmTasks('grunt-eslint'); 20 | grunt.loadNpmTasks('grunt-mocha-test'); 21 | grunt.loadNpmTasks('grunt-mocha-istanbul'); 22 | 23 | grunt.registerTask('lint', ['eslint']); 24 | grunt.registerTask('test', ['mochaTest']); 25 | grunt.registerTask('coverage', ['mocha_istanbul']); 26 | grunt.registerTask('build', ['lint', 'coverage']); 27 | }; 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Eyas Ranjous 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @datastructures-js/trie 2 | 3 | [![npm](https://img.shields.io/npm/v/@datastructures-js/trie.svg)](https://www.npmjs.com/package/@datastructures-js/trie) 4 | [![npm](https://img.shields.io/npm/dm/@datastructures-js/trie.svg)](https://www.npmjs.com/package/@datastructures-js/trie) [![npm](https://img.shields.io/badge/node-%3E=%206.0-blue.svg)](https://www.npmjs.com/package/@datastructures-js/trie) 5 | 6 | Trie implementation in javascript. Each Trie node holds one character of a word. 7 | 8 | 9 | 10 | # Contents 11 | * [Install](#install) 12 | * [require](#require) 13 | * [import](#import) 14 | * [API](#api) 15 | * [constructor](#constructor) 16 | * [insert](#insert) 17 | * [has](#has) 18 | * [find](#find) 19 | * [remove](#remove) 20 | * [forEach](#foreach) 21 | * [toArray](#toarray) 22 | * [wordsCount](#wordsCount) 23 | * [nodesCount](#nodesCount) 24 | * [clear](#clear) 25 | * [Trie.fromArray](#triefromarray) 26 | * [TrieNode](#trienode) 27 | * [Build](#build) 28 | * [License](#license) 29 | 30 | ## Install 31 | 32 | ```sh 33 | npm install --save @datastructures-js/trie 34 | ``` 35 | 36 | ## require 37 | 38 | ```js 39 | const { Trie, TrieNode } = require('@datastructures-js/trie'); 40 | ``` 41 | 42 | ## import 43 | 44 | ```js 45 | import { Trie, TrieNode } from '@datastructures-js/trie'; 46 | ``` 47 | 48 | ## API 49 | 50 | ### constructor 51 | 52 | ```js 53 | const dictionary = new Trie(); 54 | ``` 55 | 56 | ### insert 57 | insert the string form of value (`value.toString()`) into the trie. 58 | 59 | *Note: the empty string is not a default word in the trie. empty word can be added by explicitly calling `.insert('')`* 60 | 61 | ```js 62 | dictionary 63 | .insert('hi') 64 | .insert('hit') 65 | .insert('hide') 66 | .insert('hello') 67 | .insert('sand') 68 | .insert('safe') 69 | .insert('noun') 70 | .insert('name'); 71 | ``` 72 | 73 | ### has 74 | checks if a word exists in the trie. 75 | 76 | ```js 77 | dictionary.has('hi'); // true 78 | dictionary.has('sky'); // false 79 | ``` 80 | 81 | ### find 82 | finds a word in the trie and returns the node of its last character. 83 | 84 | ```js 85 | const hi = dictionary.find('hi'); 86 | // hi.getChar() = 'i' 87 | // hi.getParent().getChar() = 'h' 88 | 89 | const safe = dictionary.find('safe'); 90 | // safe.getChar() = 'e' 91 | // safe.getParent().getChar() = 'f' 92 | // safe.getParent().getParent().getChar() = 'a' 93 | 94 | const nothing = dictionary.find('nothing'); // null 95 | ``` 96 | 97 | ### remove 98 | removes a word from the trie. 99 | 100 | ```js 101 | dictionary.remove('hi'); // hi 102 | 103 | // none existing word 104 | dictionary.remove('sky'); // null 105 | ``` 106 | 107 | ### forEach 108 | traverses all words in the trie. 109 | 110 | ```js 111 | dictionary.forEach((word) => console.log(word)); 112 | 113 | /* 114 | hit 115 | hide 116 | hello 117 | sand 118 | safe 119 | noun 120 | name 121 | */ 122 | ``` 123 | 124 | ### toArray 125 | converts the trie into an array of words. 126 | 127 | ```js 128 | console.log(dictionary.toArray()); 129 | 130 | // ['hit', 'hide', 'hello', 'sand', 'safe', 'noun', 'name'] 131 | ``` 132 | 133 | ### wordsCount 134 | gets the count of words in the trie. 135 | 136 | ```js 137 | console.log(dictionary.wordsCount()); // 7 138 | ``` 139 | 140 | ### nodesCount 141 | gets the count of nodes in the trie. 142 | 143 | ```js 144 | console.log(dictionary.nodesCount()); // 23 145 | ``` 146 | 147 | ### clear 148 | clears the trie. 149 | 150 | ```js 151 | dictionary.clear(); 152 | console.log(dictionary.wordsCount()); // 0 153 | console.log(dictionary.nodesCount()); // 1 154 | ``` 155 | 156 | ### Trie.fromArray 157 | converts an existing array of values into a trie. 158 | 159 | ```js 160 | const numbersTrie = Trie.fromArray([1, 32, 123, 21, 222, 132, 111, 312]); 161 | 162 | console.log(numbersTrie.wordsCount()); // 8 163 | console.log(numbersTrie.has('132')); // true 164 | console.log(numbersTrie.has(123)); // true 165 | ``` 166 | 167 | ### TrieNode 168 | 169 | #### isRoot() 170 | checks if node is root. 171 | 172 | #### isLeaf() 173 | checks if has no children. 174 | 175 | #### getChar() 176 | gets the node's char. 177 | 178 | #### getParent() 179 | gets the node's parent node. 180 | 181 | #### setParent(node: TrieNode) 182 | sets the node's parent node. 183 | 184 | #### isEndOfWord() 185 | checks if node's char is last in a word. 186 | 187 | #### setEndOfWord(endOfWord: boolean) 188 | sets if node's char is last in a word. 189 | 190 | #### getChild(char: string) 191 | gets the node's child from a char. 192 | 193 | #### hasChild(char: string) 194 | checks if the node has a child from a char. 195 | 196 | #### childrenCount() 197 | gets the node's children count. 198 | 199 | ## Build 200 | ``` 201 | grunt build 202 | ``` 203 | 204 | ## License 205 | The MIT License. Full License is [here](https://github.com/datastructures-js/trie/blob/master/LICENSE) 206 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import { Trie } from './src/trie'; 2 | import { TrieNode } from './src/trieNode'; 3 | 4 | export { TrieNode } 5 | export { Trie } 6 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const { Trie } = require('./src/trie'); 2 | const { TrieNode } = require('./src/trieNode'); 3 | 4 | exports.TrieNode = TrieNode 5 | exports.Trie = Trie; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@datastructures-js/trie", 3 | "version": "4.2.2", 4 | "description": "trie implementation in javascript", 5 | "main": "index.js", 6 | "types": "index.d.ts", 7 | "scripts": { 8 | "test": "grunt test" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/datastructures-js/trie.git" 13 | }, 14 | "keywords": [ 15 | "trie", 16 | "trie es6", 17 | "trie js" 18 | ], 19 | "author": "Eyas Ranjous ", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/datastructures-js/trie/issues" 23 | }, 24 | "homepage": "https://github.com/datastructures-js/trie#readme", 25 | "devDependencies": { 26 | "chai": "^4.2.0", 27 | "eslint": "^6.8.0", 28 | "eslint-config-airbnb-base": "^14.0.0", 29 | "eslint-plugin-import": "^2.19.1", 30 | "grunt": "^1.4.1", 31 | "grunt-eslint": "^22.0.0", 32 | "grunt-mocha-istanbul": "^5.0.2", 33 | "grunt-mocha-test": "^0.13.3", 34 | "istanbul": "^0.4.5", 35 | "mocha": "^6.2.2" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/trie.d.ts: -------------------------------------------------------------------------------- 1 | import { TrieNode } from './trieNode'; 2 | 3 | export class Trie { 4 | insert(value: { toString: () => string }): Trie; 5 | has(value: { toString: () => string }): boolean; 6 | find(value: { toString: () => string }): TrieNode; 7 | remove(value: { toString: () => string }): string|null; 8 | forEach(cb: (word: string) => void): void; 9 | toArray(): string[]; 10 | nodesCount(): number; 11 | wordsCount(): number; 12 | clear(): void; 13 | static fromArray(words: string[]): Trie; 14 | } 15 | -------------------------------------------------------------------------------- /src/trie.js: -------------------------------------------------------------------------------- 1 | /** 2 | * datastructures-js/trie 3 | * @copyright 2020 Eyas Ranjous 4 | * @license MIT 5 | */ 6 | 7 | const { TrieNode } = require('./trieNode'); 8 | 9 | /** 10 | * @class Trie 11 | */ 12 | class Trie { 13 | constructor() { 14 | this._root = new TrieNode(''); 15 | this._wordsCount = 0; 16 | this._nodesCount = 1; // root node 17 | } 18 | 19 | /** 20 | * Inserts a word into the trie 21 | * @public 22 | * @param {any} value 23 | * @returns {Trie} 24 | */ 25 | insert(value) { 26 | if (value === undefined || value === null) { 27 | return this; 28 | } 29 | 30 | const word = value.toString(); 31 | let currentNode = this._root; 32 | for (let i = 0; i < word.length; i += 1) { 33 | if (!currentNode.hasChild(word[i])) { 34 | currentNode.addChild(word[i]); 35 | this._nodesCount += 1; 36 | } 37 | currentNode = currentNode.getChild(word[i]); 38 | } 39 | 40 | if (!currentNode.isEndOfWord()) { 41 | currentNode.setEndOfWord(true); 42 | this._wordsCount += 1; 43 | } 44 | 45 | return this; 46 | } 47 | 48 | /** 49 | * Checks if a word exists in the trie 50 | * @public 51 | * @param {any} value 52 | * @returns {boolean} 53 | */ 54 | has(value) { 55 | if (value === undefined || value === null) { 56 | return false; 57 | } 58 | 59 | const word = value.toString(); 60 | let currentNode = this._root; 61 | for (let i = 0; i < word.length; i += 1) { 62 | if (!currentNode.hasChild(word[i])) { 63 | return false; 64 | } 65 | currentNode = currentNode.getChild(word[i]); 66 | } 67 | 68 | if (!currentNode.isEndOfWord()) { 69 | return false; 70 | } 71 | 72 | return true; 73 | } 74 | 75 | /** 76 | * Finds a word in the trie and returns its last char node 77 | * @public 78 | * @param {any} value 79 | * @returns {TrieNode} 80 | */ 81 | find(value) { 82 | if (value === undefined || value === null) { 83 | return null; 84 | } 85 | 86 | const word = value.toString(); 87 | let currentNode = this._root; 88 | 89 | for (let i = 0; i < word.length; i += 1) { 90 | if (!currentNode.hasChild(word[i])) { 91 | return null; 92 | } 93 | currentNode = currentNode.getChild(word[i]); 94 | } 95 | 96 | if (!currentNode.isEndOfWord()) { 97 | return null; 98 | } 99 | 100 | return currentNode; 101 | } 102 | 103 | /** 104 | * Removes a word from the trie 105 | * @public 106 | * @param {string} word 107 | * @returns {string | null} 108 | */ 109 | remove(value) { 110 | if (value === undefined || value === null) { 111 | return null; 112 | } 113 | 114 | const word = value.toString(); 115 | let currentNode = this._root; 116 | 117 | for (let i = 0; i < word.length; i += 1) { 118 | if (!currentNode.hasChild(word[i])) { 119 | return null; 120 | } 121 | currentNode = currentNode.getChild(word[i]); 122 | } 123 | 124 | if (!currentNode.isEndOfWord()) { 125 | return null; 126 | } 127 | 128 | if (currentNode.childrenCount() > 0 || word === '') { 129 | currentNode.setEndOfWord(false); 130 | this._wordsCount -= 1; 131 | return word; 132 | } 133 | 134 | do { 135 | currentNode.getParent().removeChild(currentNode.getChar()); 136 | this._nodesCount -= 1; 137 | currentNode = currentNode.getParent(); 138 | } while ( 139 | currentNode.isLeaf() 140 | && !currentNode.isEndOfWord() 141 | && !currentNode.isRoot() 142 | ); 143 | 144 | this._wordsCount -= 1; 145 | return word; 146 | } 147 | 148 | /** 149 | * Traverse the trie and pass words to a callback 150 | * @public 151 | * @param {function} cb 152 | */ 153 | forEach(cb) { 154 | if (typeof cb !== 'function') { 155 | throw new Error('Trie.forEach expects a callback function'); 156 | } 157 | 158 | const forEachRecursive = (node = this._root, word = '') => { 159 | if (node.isEndOfWord()) { 160 | cb(word); 161 | } 162 | 163 | node.children().forEach((child) => { 164 | forEachRecursive(child, word + child.getChar()); 165 | }); 166 | }; 167 | 168 | return forEachRecursive(); 169 | } 170 | 171 | /** 172 | * Converts the trie into an array of words 173 | * @public 174 | * @returns {array} 175 | */ 176 | toArray() { 177 | const result = []; 178 | this.forEach((word) => result.push(word)); 179 | return result; 180 | } 181 | 182 | /** 183 | * @public 184 | * @returns {number} 185 | */ 186 | nodesCount() { 187 | return this._nodesCount; 188 | } 189 | 190 | /** 191 | * @public 192 | * @returns {number} 193 | */ 194 | wordsCount() { 195 | return this._wordsCount; 196 | } 197 | 198 | /** 199 | * Clears the trie 200 | * @public 201 | */ 202 | clear() { 203 | this._root = new TrieNode(''); 204 | this._nodesCount = 1; 205 | this._wordsCount = 0; 206 | } 207 | 208 | /** 209 | * Converts an existing list into a trie 210 | * @public 211 | * @static 212 | * @returns {Trie} 213 | */ 214 | static fromArray(values) { 215 | const trie = new Trie(); 216 | values.forEach((value) => trie.insert(value)); 217 | return trie; 218 | } 219 | } 220 | 221 | exports.Trie = Trie; 222 | -------------------------------------------------------------------------------- /src/trieNode.d.ts: -------------------------------------------------------------------------------- 1 | export class TrieNode { 2 | constructor(char: string); 3 | isRoot(): boolean; 4 | getChar(): string; 5 | setParent(parent: TrieNode): TrieNode; 6 | getParent(): TrieNode; 7 | setEndOfWord(isEndOfWord: boolean): TrieNode; 8 | isEndOfWord(): boolean; 9 | addChild(char: string): TrieNode; 10 | removeChild(char: string): boolean; 11 | getChild(char: string): TrieNode; 12 | hasChild(char: string): boolean; 13 | children(): Map; 14 | childrenCount(): number; 15 | } 16 | -------------------------------------------------------------------------------- /src/trieNode.js: -------------------------------------------------------------------------------- 1 | /** 2 | * datastructures-js/trie 3 | * @copyright 2020 Eyas Ranjous 4 | * @license MIT 5 | * 6 | * @class TrieNode 7 | */ 8 | class TrieNode { 9 | constructor(char) { 10 | this._char = char; 11 | this._isEndOfWord = false; 12 | this._parent = null; 13 | this._children = new Map(); 14 | } 15 | 16 | /** 17 | * @public 18 | * @return {boolean} 19 | */ 20 | isRoot() { 21 | return this._char === ''; 22 | } 23 | 24 | /** 25 | * @public 26 | * @return {boolean} 27 | */ 28 | isLeaf() { 29 | return this._children.size === 0; 30 | } 31 | 32 | /** 33 | * @public 34 | * @returns {string} 35 | */ 36 | getChar() { 37 | return this._char; 38 | } 39 | 40 | /** 41 | * @internal 42 | * @param {TrieNode} parentNode 43 | */ 44 | setParent(parentNode) { 45 | this._parent = parentNode; 46 | return this; 47 | } 48 | 49 | /** 50 | * @public 51 | * @return {TrieNode} 52 | */ 53 | getParent() { 54 | return this._parent; 55 | } 56 | 57 | /** 58 | * @internal 59 | * @param {boolean} isEndOfWord 60 | */ 61 | setEndOfWord(isEndOfWord) { 62 | this._isEndOfWord = isEndOfWord; 63 | return this; 64 | } 65 | 66 | /** 67 | * @public 68 | * @return {boolean} 69 | */ 70 | isEndOfWord() { 71 | return this._isEndOfWord; 72 | } 73 | 74 | /** 75 | * @internal 76 | * @param {string} char 77 | */ 78 | addChild(char) { 79 | const childNode = new TrieNode(char); 80 | childNode.setParent(this); 81 | this._children.set(char, childNode); 82 | return this; 83 | } 84 | 85 | /** 86 | * @internal 87 | * @param {string} char 88 | * @return {boolean} 89 | */ 90 | removeChild(char) { 91 | return this._children.delete(char); 92 | } 93 | 94 | /** 95 | * @public 96 | * @param {string} char 97 | * @return {TrieNode} 98 | */ 99 | getChild(char) { 100 | return this._children.get(char) || null; 101 | } 102 | 103 | /** 104 | * @public 105 | * @param {string} char 106 | * @return {boolean} 107 | */ 108 | hasChild(char) { 109 | return this._children.has(char); 110 | } 111 | 112 | /** 113 | * @internal 114 | * @return {Map} 115 | */ 116 | children() { 117 | return this._children; 118 | } 119 | 120 | /** 121 | * @public 122 | * @return {number} 123 | */ 124 | childrenCount() { 125 | return this._children.size; 126 | } 127 | } 128 | 129 | exports.TrieNode = TrieNode; 130 | -------------------------------------------------------------------------------- /test/trie.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai'); 2 | const { TrieNode } = require('../src/trieNode'); 3 | const { Trie } = require('../src/trie'); 4 | 5 | describe('Trie unit tests', () => { 6 | const trie = new Trie(); 7 | 8 | describe('.insert(word)', () => { 9 | it('insert words into the trie', () => { 10 | expect(trie.insert()).to.be.instanceof(Trie); // does not insert undefined 11 | expect(trie.insert('hi')).to.be.instanceof(Trie); 12 | expect(trie.insert('hi')).to.be.instanceof(Trie); 13 | expect(trie.insert('hi')).to.be.instanceof(Trie); 14 | expect(trie.insert('hi')).to.be.instanceof(Trie); 15 | expect(trie.insert('hit')).to.be.instanceof(Trie); 16 | expect(trie.insert('hide')).to.be.instanceof(Trie); 17 | expect(trie.insert('hello')).to.be.instanceof(Trie); 18 | expect(trie.insert('sand')).to.be.instanceof(Trie); 19 | expect(trie.insert('safe')).to.be.instanceof(Trie); 20 | expect(trie.insert('noun')).to.be.instanceof(Trie); 21 | expect(trie.insert('name')).to.be.instanceof(Trie); 22 | 23 | // empty string can be inserted explicitly as a word, root is its node 24 | expect(trie.insert('')).to.be.instanceof(Trie); 25 | }); 26 | }); 27 | 28 | describe('.nodesCount()', () => { 29 | it('should get the count of characters', () => { 30 | expect(trie.nodesCount()).to.equal(23); 31 | }); 32 | }); 33 | 34 | describe('.wordsCount()', () => { 35 | it('should get the count of words', () => { 36 | expect(trie.wordsCount()).to.equal(9); 37 | }); 38 | }); 39 | 40 | describe('.has(word)', () => { 41 | it('returns true for existing words', () => { 42 | expect(trie.has('hi')).to.equal(true); 43 | expect(trie.has('hello')).to.equal(true); 44 | expect(trie.has('hit')).to.equal(true); 45 | expect(trie.has('hide')).to.equal(true); 46 | expect(trie.has('name')).to.equal(true); 47 | expect(trie.has('noun')).to.equal(true); 48 | expect(trie.has('sand')).to.equal(true); 49 | expect(trie.has('safe')).to.equal(true); 50 | expect(trie.has('')).to.equal(true); 51 | }); 52 | 53 | it('returns false for none existing words', () => { 54 | expect(trie.has()).to.equal(false); 55 | expect(trie.has(null)).to.equal(false); 56 | expect(trie.has('his')).to.equal(false); 57 | expect(trie.has('helo')).to.equal(false); 58 | expect(trie.has('hitt')).to.equal(false); 59 | expect(trie.has('nnnn')).to.equal(false); 60 | expect(trie.has('h')).to.equal(false); 61 | expect(trie.has('san')).to.equal(false); 62 | expect(trie.has(123)).to.equal(false); 63 | }); 64 | }); 65 | 66 | describe('.find(word)', () => { 67 | it('finds a word in the trie', () => { 68 | const hi = trie.find('hi'); 69 | expect(hi).to.be.instanceof(TrieNode); 70 | expect(hi.getChar()).to.equal('i'); 71 | }); 72 | 73 | it('returns null for non existing words', () => { 74 | expect(trie.find()).to.equal(null); 75 | expect(trie.find(null)).to.equal(null); 76 | expect(trie.find('hex')).to.equal(null); 77 | expect(trie.find('h')).to.equal(null); 78 | expect(trie.find(123)).to.equal(null); 79 | }); 80 | }); 81 | 82 | describe('.forEach(cb)', () => { 83 | it('traverse all words in the trie', () => { 84 | const words = []; 85 | trie.forEach((word) => words.push(word)); 86 | expect(words).to.have.lengthOf(9).and.to.have.members([ 87 | '', 88 | 'hi', 89 | 'hit', 90 | 'hide', 91 | 'hello', 92 | 'sand', 93 | 'safe', 94 | 'noun', 95 | 'name' 96 | ]); 97 | }); 98 | 99 | it('throws an error if callback is not a function', () => { 100 | expect(() => trie.forEach()).to.throw(Error) 101 | .and.to.have.property( 102 | 'message', 103 | 'Trie.forEach expects a callback function' 104 | ); 105 | }); 106 | }); 107 | 108 | describe('.toArray(cb)', () => { 109 | it('converts the trie into an array of words', () => { 110 | expect(trie.toArray()).to.have.lengthOf(9).and.to.have.members([ 111 | '', 112 | 'hi', 113 | 'hit', 114 | 'hide', 115 | 'hello', 116 | 'sand', 117 | 'safe', 118 | 'noun', 119 | 'name' 120 | ]); 121 | }); 122 | }); 123 | 124 | describe('.remove(word)', () => { 125 | it('remove words from the trie', () => { 126 | trie.remove('hit'); 127 | expect(trie.has('hit')).to.equal(false); 128 | expect(trie.nodesCount()).to.equal(22); 129 | expect(trie.wordsCount()).to.equal(8); 130 | 131 | trie.remove('hide'); 132 | expect(trie.has('hide')).to.equal(false); 133 | expect(trie.nodesCount()).to.equal(20); 134 | expect(trie.wordsCount()).to.equal(7); 135 | 136 | trie.remove('hi'); 137 | expect(trie.has('hi')).to.equal(false); 138 | expect(trie.nodesCount()).to.equal(19); 139 | expect(trie.wordsCount()).to.equal(6); 140 | 141 | trie.remove('hello'); 142 | expect(trie.has('hello')).to.equal(false); 143 | expect(trie.nodesCount()).to.equal(14); 144 | expect(trie.wordsCount()).to.equal(5); 145 | 146 | trie.remove('safe'); 147 | expect(trie.has('safe')).to.equal(false); 148 | expect(trie.nodesCount()).to.equal(12); 149 | expect(trie.wordsCount()).to.equal(4); 150 | 151 | trie.remove('sand'); 152 | expect(trie.has('sand')).to.equal(false); 153 | expect(trie.nodesCount()).to.equal(8); 154 | expect(trie.wordsCount()).to.equal(3); 155 | 156 | trie.remove('noun'); 157 | expect(trie.has('noun')).to.equal(false); 158 | expect(trie.nodesCount()).to.equal(5); 159 | expect(trie.wordsCount()).to.equal(2); 160 | 161 | trie.remove('name'); 162 | expect(trie.has('name')).to.equal(false); 163 | expect(trie.nodesCount()).to.equal(1); 164 | expect(trie.wordsCount()).to.equal(1); 165 | 166 | trie.remove(''); 167 | expect(trie.has('')).to.equal(false); 168 | expect(trie.nodesCount()).to.equal(1); 169 | expect(trie.wordsCount()).to.equal(0); 170 | 171 | expect(trie.remove(123)).to.equal(null); 172 | expect(trie.nodesCount()).to.equal(1); 173 | expect(trie.wordsCount()).to.equal(0); 174 | }); 175 | 176 | it('returns null when removing none existing word', () => { 177 | trie.insert('name'); 178 | expect(trie.remove('na')).to.equal(null); 179 | expect(trie.nodesCount()).to.equal(5); 180 | expect(trie.wordsCount()).to.equal(1); 181 | expect(trie.remove('something')).to.equal(null); 182 | expect(trie.remove()).to.equal(null); 183 | expect(trie.remove(null)).to.equal(null); 184 | }); 185 | }); 186 | 187 | describe('.clear()', () => { 188 | it('clears the trie', () => { 189 | trie.insert('test'); 190 | trie.clear(); 191 | expect(trie.has('test')).to.equal(false); 192 | expect(trie.nodesCount()).to.equal(1); 193 | expect(trie.wordsCount()).to.equal(0); 194 | }); 195 | }); 196 | 197 | describe('.fromArray(values)', () => { 198 | it('convert an existing list of values into a trie', () => { 199 | const numbers = [1, 32, 123, 21, 222, 132, 111, 312]; 200 | const numbersTrie = Trie.fromArray(numbers); 201 | expect(numbersTrie.wordsCount()).to.equal(8); 202 | expect(numbersTrie.nodesCount()).to.equal(16); 203 | expect(numbersTrie.has(123)).to.equal(true); 204 | expect(numbersTrie.has('123')).to.equal(true); 205 | expect(numbersTrie.has(222)).to.equal(true); 206 | expect(numbersTrie.has('222')).to.equal(true); 207 | expect(numbersTrie.has('20')).to.equal(false); 208 | }); 209 | }); 210 | }); 211 | --------------------------------------------------------------------------------