├── .codeclimate.yml ├── packages ├── useTrie │ ├── .npmignore │ ├── cshooks-simple-demo.gif │ ├── .prettierrc │ ├── tsconfig.json │ ├── jest.config.js │ ├── LICENSE │ ├── .eslintrc.js │ ├── package.json │ ├── src │ │ └── index.ts │ ├── README.md │ └── __tests__ │ │ └── index.test.ts └── useHeap │ ├── README.md │ ├── .rts2_cache_esm │ └── rpt2_6c5aa84371fb4d0b540893c7ed8ea53c9d61186b │ │ ├── types │ │ └── cache │ │ │ ├── 04930a1efb0aac06a1ed194ee4488d1850d00a9a │ │ │ ├── 0cb277f93bda0a1e92531bdaa3b520710a18b96c │ │ │ ├── 0dfd352485e1cde27b4dbd1ad59422bea84caafe │ │ │ ├── 13ed152c3bb633f698cdc549c99f09e35a7ecd54 │ │ │ ├── 1ec80f7815d9c1fccb542cb65f6a2519e086254e │ │ │ ├── 229e2decf5996242eaffc54d79e2d25dbde5338c │ │ │ ├── 270014a560d9de4bfb38ad23193307d2f2f397bf │ │ │ ├── 28c1c69a008209582702d31ec4ce729ab5bb01c2 │ │ │ ├── 3c7d67f45dc220ed135b5f5ec48d970d241e0ccb │ │ │ ├── 429e09175cab9a82e48b7503d776f362ded9a3a1 │ │ │ ├── 51b43d64eb711efb49b88a8609ad566c1245cb1b │ │ │ ├── 5ab559ab080ffb3183cd5aed63ba5f049eae069a │ │ │ ├── 6f00ded12e125817a93816c313053e6997d241e6 │ │ │ ├── 7287db9c14739132bb8150429014535c8605c78e │ │ │ ├── 7321d0dfbdb75438cb2e138b583efdf117879208 │ │ │ ├── 79d959ef85965d4eabe8812bb6cd8e174025b7f3 │ │ │ ├── 88211eb3fa628bb865a4e33c845ff415edb24799 │ │ │ ├── 96c785184063feaa969f1c06f34db4d96b260971 │ │ │ ├── a9929d07729ac4a8903d81065b7828e835cdc7b9 │ │ │ ├── b23aac01625cb7eca2da583b9fd6862d86eb496b │ │ │ ├── b42c249b3ba3712a376ff2f0018bc46ab7e78cdc │ │ │ ├── cc364ffea66cc4ff29fe8dfa11d1c094af0ffad6 │ │ │ ├── d6331ff74a97dd34611d2132951c0467cbd5c99d │ │ │ ├── e0366d908162d1119c1701c22c78e36f802441e6 │ │ │ ├── e17ffa7570dc8b8a769104be4fef4dd5aa791c79 │ │ │ ├── e9bc6323f362cba3d6da40fffe647f7c60c3debb │ │ │ └── f72b93f25793fb2dc8cf6b59ea38a6a842ead775 │ │ ├── semanticDiagnostics │ │ └── cache │ │ │ └── 78a2aba1afee055cc773ba110f69e3165fa9282a │ │ ├── syntacticDiagnostics │ │ └── cache │ │ │ └── 78a2aba1afee055cc773ba110f69e3165fa9282a │ │ └── code │ │ └── cache │ │ └── 78a2aba1afee055cc773ba110f69e3165fa9282a │ ├── tsconfig.json │ ├── LICENSE │ ├── package.json │ ├── .gitignore │ ├── .size-snapshot.json │ ├── test │ └── index.test.ts │ └── src │ └── index.ts ├── lerna.json ├── tsconfig.json ├── package.json ├── .vscode └── settings.json ├── LICENSE ├── .all-contributorsrc ├── .gitignore └── README.md /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | exclude_patterns: 3 | - "**/__tests__/" 4 | - "**/vendor/" 5 | -------------------------------------------------------------------------------- /packages/useTrie/.npmignore: -------------------------------------------------------------------------------- 1 | /.* 2 | /*.gif 3 | /tsconfig.json 4 | /tslint.json 5 | /*.lock 6 | /src 7 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "npmClient": "npm", 3 | "packages": ["packages/*", "integration"], 4 | "version": "independent" 5 | } 6 | -------------------------------------------------------------------------------- /packages/useHeap/README.md: -------------------------------------------------------------------------------- 1 | # @cshooks/useheap 2 | 3 | Returns a Heap data structure (you can use it for Priority Queue) 4 | -------------------------------------------------------------------------------- /packages/useHeap/.rts2_cache_esm/rpt2_6c5aa84371fb4d0b540893c7ed8ea53c9d61186b/types/cache/04930a1efb0aac06a1ed194ee4488d1850d00a9a: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/useHeap/.rts2_cache_esm/rpt2_6c5aa84371fb4d0b540893c7ed8ea53c9d61186b/types/cache/0cb277f93bda0a1e92531bdaa3b520710a18b96c: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/useHeap/.rts2_cache_esm/rpt2_6c5aa84371fb4d0b540893c7ed8ea53c9d61186b/types/cache/0dfd352485e1cde27b4dbd1ad59422bea84caafe: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/useHeap/.rts2_cache_esm/rpt2_6c5aa84371fb4d0b540893c7ed8ea53c9d61186b/types/cache/13ed152c3bb633f698cdc549c99f09e35a7ecd54: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/useHeap/.rts2_cache_esm/rpt2_6c5aa84371fb4d0b540893c7ed8ea53c9d61186b/types/cache/1ec80f7815d9c1fccb542cb65f6a2519e086254e: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/useHeap/.rts2_cache_esm/rpt2_6c5aa84371fb4d0b540893c7ed8ea53c9d61186b/types/cache/229e2decf5996242eaffc54d79e2d25dbde5338c: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/useHeap/.rts2_cache_esm/rpt2_6c5aa84371fb4d0b540893c7ed8ea53c9d61186b/types/cache/270014a560d9de4bfb38ad23193307d2f2f397bf: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/useHeap/.rts2_cache_esm/rpt2_6c5aa84371fb4d0b540893c7ed8ea53c9d61186b/types/cache/28c1c69a008209582702d31ec4ce729ab5bb01c2: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/useHeap/.rts2_cache_esm/rpt2_6c5aa84371fb4d0b540893c7ed8ea53c9d61186b/types/cache/3c7d67f45dc220ed135b5f5ec48d970d241e0ccb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/useHeap/.rts2_cache_esm/rpt2_6c5aa84371fb4d0b540893c7ed8ea53c9d61186b/types/cache/429e09175cab9a82e48b7503d776f362ded9a3a1: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/useHeap/.rts2_cache_esm/rpt2_6c5aa84371fb4d0b540893c7ed8ea53c9d61186b/types/cache/51b43d64eb711efb49b88a8609ad566c1245cb1b: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/useHeap/.rts2_cache_esm/rpt2_6c5aa84371fb4d0b540893c7ed8ea53c9d61186b/types/cache/5ab559ab080ffb3183cd5aed63ba5f049eae069a: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/useHeap/.rts2_cache_esm/rpt2_6c5aa84371fb4d0b540893c7ed8ea53c9d61186b/types/cache/6f00ded12e125817a93816c313053e6997d241e6: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/useHeap/.rts2_cache_esm/rpt2_6c5aa84371fb4d0b540893c7ed8ea53c9d61186b/types/cache/7287db9c14739132bb8150429014535c8605c78e: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/useHeap/.rts2_cache_esm/rpt2_6c5aa84371fb4d0b540893c7ed8ea53c9d61186b/types/cache/7321d0dfbdb75438cb2e138b583efdf117879208: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/useHeap/.rts2_cache_esm/rpt2_6c5aa84371fb4d0b540893c7ed8ea53c9d61186b/types/cache/79d959ef85965d4eabe8812bb6cd8e174025b7f3: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/useHeap/.rts2_cache_esm/rpt2_6c5aa84371fb4d0b540893c7ed8ea53c9d61186b/types/cache/88211eb3fa628bb865a4e33c845ff415edb24799: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/useHeap/.rts2_cache_esm/rpt2_6c5aa84371fb4d0b540893c7ed8ea53c9d61186b/types/cache/96c785184063feaa969f1c06f34db4d96b260971: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/useHeap/.rts2_cache_esm/rpt2_6c5aa84371fb4d0b540893c7ed8ea53c9d61186b/types/cache/a9929d07729ac4a8903d81065b7828e835cdc7b9: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/useHeap/.rts2_cache_esm/rpt2_6c5aa84371fb4d0b540893c7ed8ea53c9d61186b/types/cache/b23aac01625cb7eca2da583b9fd6862d86eb496b: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/useHeap/.rts2_cache_esm/rpt2_6c5aa84371fb4d0b540893c7ed8ea53c9d61186b/types/cache/b42c249b3ba3712a376ff2f0018bc46ab7e78cdc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/useHeap/.rts2_cache_esm/rpt2_6c5aa84371fb4d0b540893c7ed8ea53c9d61186b/types/cache/cc364ffea66cc4ff29fe8dfa11d1c094af0ffad6: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/useHeap/.rts2_cache_esm/rpt2_6c5aa84371fb4d0b540893c7ed8ea53c9d61186b/types/cache/d6331ff74a97dd34611d2132951c0467cbd5c99d: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/useHeap/.rts2_cache_esm/rpt2_6c5aa84371fb4d0b540893c7ed8ea53c9d61186b/types/cache/e0366d908162d1119c1701c22c78e36f802441e6: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/useHeap/.rts2_cache_esm/rpt2_6c5aa84371fb4d0b540893c7ed8ea53c9d61186b/types/cache/e17ffa7570dc8b8a769104be4fef4dd5aa791c79: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/useHeap/.rts2_cache_esm/rpt2_6c5aa84371fb4d0b540893c7ed8ea53c9d61186b/types/cache/e9bc6323f362cba3d6da40fffe647f7c60c3debb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/useHeap/.rts2_cache_esm/rpt2_6c5aa84371fb4d0b540893c7ed8ea53c9d61186b/types/cache/f72b93f25793fb2dc8cf6b59ea38a6a842ead775: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/useTrie/cshooks-simple-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cshooks/hooks/HEAD/packages/useTrie/cshooks-simple-demo.gif -------------------------------------------------------------------------------- /packages/useHeap/.rts2_cache_esm/rpt2_6c5aa84371fb4d0b540893c7ed8ea53c9d61186b/semanticDiagnostics/cache/78a2aba1afee055cc773ba110f69e3165fa9282a: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /packages/useHeap/.rts2_cache_esm/rpt2_6c5aa84371fb4d0b540893c7ed8ea53c9d61186b/syntacticDiagnostics/cache/78a2aba1afee055cc773ba110f69e3165fa9282a: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /packages/useTrie/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 2, 4 | "semi": true, 5 | "singleQuote": true, 6 | "endOfLine": "lf" 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "strict": true, 6 | "allowSyntheticDefaultImports": true, 7 | "esModuleInterop": true 8 | }, 9 | "compileOnSave": false, 10 | "buildOnSave": false 11 | } 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "private": true, 4 | "scripts": { 5 | "publish:alpha": "lerna publish --preid alpha" 6 | }, 7 | "devDependencies": { 8 | "@types/jest": "^24.0.18", 9 | "eslint": "^6.5.1", 10 | "lerna": "^3.16.5", 11 | "typescript": "^3.6.3" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/useTrie/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./lib", 5 | "declaration": true, 6 | "declarationDir": "lib", 7 | "noLib": false, 8 | "jsx": "react", 9 | "sourceMap": true, 10 | "downlevelIteration": true 11 | }, 12 | "include": ["./src"] 13 | } 14 | -------------------------------------------------------------------------------- /packages/useTrie/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // roots: ["__tests__"], 3 | transform: { 4 | '^.+\\.tsx?$': 'ts-jest', 5 | }, 6 | testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$', 7 | // testRegex: "(/__tests__/**/*.ts?(x),**/?(*.)+(spec|test).ts?(x)", 8 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], 9 | // https://medium.com/@mtiller/debugging-with-typescript-jest-ts-jest-and-visual-studio-code-ef9ca8644132 10 | // collectCoverage: true, 11 | collectCoverageFrom: ['./src/index.ts'], 12 | }; 13 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "workbench.colorCustomizations": { 3 | "[Cobalt2]": {}, 4 | "activityBar.background": "#1accff", 5 | "activityBar.foreground": "#15202b", 6 | "activityBar.inactiveForeground": "#15202b99", 7 | "activityBarBadge.background": "#df00ad", 8 | "activityBarBadge.foreground": "#e7e7e7", 9 | "titleBar.activeBackground": "#00b3e6", 10 | "titleBar.inactiveBackground": "#00b3e699", 11 | "titleBar.activeForeground": "#15202b", 12 | "titleBar.inactiveForeground": "#15202b99", 13 | "statusBar.background": "#00b3e6", 14 | "statusBarItem.hoverBackground": "#008bb3", 15 | "statusBar.foreground": "#15202b" 16 | }, 17 | "window.titleBarStyle": "custom", 18 | "peacock.color": "#00b3e6" 19 | } 20 | -------------------------------------------------------------------------------- /packages/useHeap/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "ESNext", 5 | "lib": ["dom", "esnext"], 6 | "declaration": true, 7 | "sourceMap": true, 8 | "rootDir": "./", 9 | "strict": true, 10 | "noImplicitAny": true, 11 | "strictNullChecks": true, 12 | "strictFunctionTypes": true, 13 | "strictPropertyInitialization": true, 14 | "noImplicitThis": true, 15 | "alwaysStrict": true, 16 | "noUnusedLocals": true, 17 | "noUnusedParameters": true, 18 | "noImplicitReturns": true, 19 | "noFallthroughCasesInSwitch": true, 20 | "moduleResolution": "node", 21 | "baseUrl": "./", 22 | "paths": { 23 | "*": ["src/*", "node_modules/*"] 24 | }, 25 | "jsx": "react", 26 | "esModuleInterop": true 27 | }, 28 | "include": ["src", "types"], 29 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Sung M. Kim 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. 22 | -------------------------------------------------------------------------------- /packages/useHeap/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Sung M. Kim 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. 22 | -------------------------------------------------------------------------------- /packages/useTrie/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Sung M. Kim 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. 22 | -------------------------------------------------------------------------------- /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "hooks", 3 | "projectOwner": "cshooks", 4 | "repoType": "github", 5 | "repoHost": "https://github.com", 6 | "files": [ 7 | "README.md" 8 | ], 9 | "imageSize": 100, 10 | "commit": false, 11 | "contributors": [ 12 | { 13 | "login": "dance2die", 14 | "name": "Sung M. Kim", 15 | "avatar_url": "https://avatars1.githubusercontent.com/u/8465237?v=4", 16 | "profile": "https://twitter.com/dance2die", 17 | "contributions": [ 18 | "infra", 19 | "tool", 20 | "test", 21 | "doc", 22 | "ideas", 23 | "code" 24 | ] 25 | }, 26 | { 27 | "login": "nickytonline", 28 | "name": "Nick Taylor", 29 | "avatar_url": "https://avatars2.githubusercontent.com/u/833231?v=4", 30 | "profile": "https://iamdeveloper.com", 31 | "contributions": [ 32 | "review", 33 | "code", 34 | "infra", 35 | "test" 36 | ] 37 | }, 38 | { 39 | "login": "MinimumViablePerson", 40 | "name": "Nicolas Marcora", 41 | "avatar_url": "https://avatars3.githubusercontent.com/u/16916098?v=4", 42 | "profile": "https://github.com/MinimumViablePerson", 43 | "contributions": [ 44 | "ideas" 45 | ] 46 | } 47 | ], 48 | "contributorsPerLine": 7 49 | } 50 | -------------------------------------------------------------------------------- /packages/useHeap/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cshooks/useheap", 3 | "version": "0.0.1-alpha.4", 4 | "description": "A React Hook for Min/Max Heap or Priority Queue", 5 | "author": "dance2die ", 6 | "homepage": "https://github.com/cshooks/hooks/tree/master/packages/useHeap", 7 | "license": "MIT", 8 | "main": "dist/index.js", 9 | "umd:main": "dist/useheap.umd.production.js", 10 | "module": "dist/useheap.es.production.js", 11 | "typings": "dist/index.d.ts", 12 | "files": [ 13 | "dist" 14 | ], 15 | "scripts": { 16 | "start": "tsdx watch", 17 | "build": "tsdx build", 18 | "prepare": "npm run build", 19 | "test": "tsdx test" 20 | }, 21 | "devDependencies": { 22 | "@testing-library/react": "^9.3.0", 23 | "@testing-library/react-hooks": "^2.0.3", 24 | "@types/jest": "^24.0.18", 25 | "@types/react": "^16.9.5", 26 | "@types/react-dom": "^16.9.1", 27 | "react": "^16.10.2", 28 | "react-dom": "^16.10.2", 29 | "react-test-renderer": "^16.10.2", 30 | "tsdx": "^0.9.3", 31 | "typescript": "^3.6.3" 32 | }, 33 | "peerDependencies": { 34 | "react": "^16.10.2" 35 | }, 36 | "publishConfig": { 37 | "access": "public" 38 | }, 39 | "keywords": [ 40 | "react", 41 | "hooks", 42 | "heap", 43 | "cs", 44 | "priorityqueue", 45 | "priority queue" 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /packages/useTrie/.eslintrc.js: -------------------------------------------------------------------------------- 1 | //https://dev.to/robertcoopercode/using-eslint-and-prettier-in-a-typescript-project-53jb 2 | module.exports = { 3 | parser: '@typescript-eslint/parser', // Specifies the ESLint parser 4 | plugin: ['jest'], 5 | extends: [ 6 | 'plugin:jest/recommended', 7 | 'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin 8 | 'prettier/@typescript-eslint', // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier 9 | 'plugin:prettier/recommended', // Enables eslint-plugin-prettier and displays prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. 10 | ], 11 | parserOptions: { 12 | ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features 13 | sourceType: 'module', // Allows for the use of imports 14 | ecmaFeatures: { 15 | jsx: true, // Allows for the parsing of JSX 16 | }, 17 | }, 18 | rules: { 19 | // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs 20 | // e.g. "@typescript-eslint/explicit-function-return-type": "off", 21 | }, 22 | settings: { 23 | react: { 24 | version: 'detect', // Tells eslint-plugin-react to automatically detect the version of React to use 25 | }, 26 | }, 27 | }; 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Custom 2 | # custom 3 | lerna-debug.log 4 | npm-debug.log 5 | packages/*/lib 6 | 7 | 8 | 9 | # Logs 10 | logs 11 | *.log 12 | npm-debug.log* 13 | yarn-debug.log* 14 | yarn-error.log* 15 | 16 | # Runtime data 17 | pids 18 | *.pid 19 | *.seed 20 | *.pid.lock 21 | 22 | # Directory for instrumented libs generated by jscoverage/JSCover 23 | lib-cov 24 | 25 | # Coverage directory used by tools like istanbul 26 | coverage 27 | 28 | # nyc test coverage 29 | .nyc_output 30 | 31 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 32 | .grunt 33 | 34 | # Bower dependency directory (https://bower.io/) 35 | bower_components 36 | 37 | # node-waf configuration 38 | .lock-wscript 39 | 40 | # Compiled binary addons (https://nodejs.org/api/addons.html) 41 | build/Release 42 | 43 | # Dependency directories 44 | node_modules/ 45 | jspm_packages/ 46 | 47 | # TypeScript v1 declaration files 48 | typings/ 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Optional REPL history 57 | .node_repl_history 58 | 59 | # Output of 'npm pack' 60 | *.tgz 61 | 62 | # Yarn Integrity file 63 | .yarn-integrity 64 | 65 | # dotenv environment variables file 66 | .env 67 | .env.test 68 | 69 | # parcel-bundler cache (https://parceljs.org/) 70 | .cache 71 | 72 | # next.js build output 73 | .next 74 | 75 | # nuxt.js build output 76 | .nuxt 77 | 78 | # vuepress build output 79 | .vuepress/dist 80 | 81 | # Serverless directories 82 | .serverless/ 83 | 84 | # FuseBox cache 85 | .fusebox/ 86 | 87 | # DynamoDB Local files 88 | .dynamodb/ 89 | -------------------------------------------------------------------------------- /packages/useHeap/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by tsdx 2 | *.log 3 | .DS_Store 4 | node_modules 5 | .rts2_cache_cjs 6 | .rts2_cache_es 7 | .rts2_cache_umd 8 | .rts2_cache_esm 9 | dist 10 | 11 | 12 | # Logs 13 | logs 14 | *.log 15 | npm-debug.log* 16 | yarn-debug.log* 17 | yarn-error.log* 18 | 19 | # Runtime data 20 | pids 21 | *.pid 22 | *.seed 23 | *.pid.lock 24 | 25 | # Directory for instrumented libs generated by jscoverage/JSCover 26 | lib-cov 27 | 28 | # Coverage directory used by tools like istanbul 29 | coverage 30 | 31 | # nyc test coverage 32 | .nyc_output 33 | 34 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 35 | .grunt 36 | 37 | # Bower dependency directory (https://bower.io/) 38 | bower_components 39 | 40 | # node-waf configuration 41 | .lock-wscript 42 | 43 | # Compiled binary addons (https://nodejs.org/api/addons.html) 44 | build/Release 45 | 46 | # Dependency directories 47 | node_modules/ 48 | jspm_packages/ 49 | 50 | # TypeScript v1 declaration files 51 | typings/ 52 | 53 | # Optional npm cache directory 54 | .npm 55 | 56 | # Optional eslint cache 57 | .eslintcache 58 | 59 | # Optional REPL history 60 | .node_repl_history 61 | 62 | # Output of 'npm pack' 63 | *.tgz 64 | 65 | # Yarn Integrity file 66 | .yarn-integrity 67 | 68 | # dotenv environment variables file 69 | .env 70 | .env.test 71 | 72 | # parcel-bundler cache (https://parceljs.org/) 73 | .cache 74 | 75 | # next.js build output 76 | .next 77 | 78 | # nuxt.js build output 79 | .nuxt 80 | 81 | # vuepress build output 82 | .vuepress/dist 83 | 84 | # Serverless directories 85 | .serverless/ 86 | 87 | # FuseBox cache 88 | .fusebox/ 89 | 90 | # DynamoDB Local files 91 | .dynamodb/ 92 | -------------------------------------------------------------------------------- /packages/useHeap/.size-snapshot.json: -------------------------------------------------------------------------------- 1 | { 2 | "/home/dance2die/src/github/cshooks/hooks/packages/useHeap/dist/useheap.cjs.development.js": { 3 | "bundled": 5727, 4 | "minified": 1687, 5 | "gzipped": 708 6 | }, 7 | "/home/dance2die/src/github/cshooks/hooks/packages/useHeap/dist/useheap.cjs.production.js": { 8 | "bundled": 5727, 9 | "minified": 1687, 10 | "gzipped": 708 11 | }, 12 | "/home/dance2die/src/github/cshooks/hooks/packages/useHeap/dist/useheap.umd.development.js": { 13 | "bundled": 6242, 14 | "minified": 1399, 15 | "gzipped": 693 16 | }, 17 | "/home/dance2die/src/github/cshooks/hooks/packages/useHeap/dist/useheap.umd.production.js": { 18 | "bundled": 6242, 19 | "minified": 1399, 20 | "gzipped": 693 21 | }, 22 | "/home/dance2die/src/github/cshooks/hooks/packages/useHeap/dist/useheap.es.production.js": { 23 | "bundled": 5566, 24 | "minified": 1560, 25 | "gzipped": 656, 26 | "treeshaked": { 27 | "rollup": { 28 | "code": 14, 29 | "import_statements": 14 30 | }, 31 | "webpack": { 32 | "code": 998 33 | } 34 | } 35 | }, 36 | "c:\\misc\\src\\github\\dance2die\\hooks\\packages\\useHeap\\dist/useheap.cjs.development.js": { 37 | "bundled": 5727, 38 | "minified": 1687, 39 | "gzipped": 708 40 | }, 41 | "c:\\misc\\src\\github\\dance2die\\hooks\\packages\\useHeap\\dist/useheap.umd.development.js": { 42 | "bundled": 6242, 43 | "minified": 1399, 44 | "gzipped": 693 45 | }, 46 | "c:\\misc\\src\\github\\dance2die\\hooks\\packages\\useHeap\\dist/useheap.umd.production.js": { 47 | "bundled": 6242, 48 | "minified": 1399, 49 | "gzipped": 693 50 | }, 51 | "c:\\misc\\src\\github\\dance2die\\hooks\\packages\\useHeap\\dist/useheap.cjs.production.js": { 52 | "bundled": 5727, 53 | "minified": 1687, 54 | "gzipped": 708 55 | }, 56 | "c:\\misc\\src\\github\\dance2die\\hooks\\packages\\useHeap\\dist/useheap.es.production.js": { 57 | "bundled": 5566, 58 | "minified": 1560, 59 | "gzipped": 656, 60 | "treeshaked": { 61 | "rollup": { 62 | "code": 14, 63 | "import_statements": 14 64 | }, 65 | "webpack": { 66 | "code": 998 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /packages/useTrie/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cshooks/usetrie", 3 | "version": "1.0.8", 4 | "description": "React Hooks for using a Trie data structure", 5 | "author": "dance2die ", 6 | "homepage": "https://github.com/cshooks/hooks", 7 | "license": "MIT", 8 | "main": "lib/index.js", 9 | "typings": "lib/index.d.ts", 10 | "scripts": { 11 | "prepublish": "npm run prepare", 12 | "clean": "rimraf lib", 13 | "build": " run-s clean build:*", 14 | "build:src": "tsc", 15 | "build:uglify": "uglifyjs lib/index.js -o lib/index.js -c --keep-fnames -m", 16 | "prepare": "run-s clean build", 17 | "test": "jest --coverage", 18 | "test:watch": "jest --watch", 19 | "format": "prettier" 20 | }, 21 | "devDependencies": { 22 | "@testing-library/react": "^9.3.0", 23 | "@testing-library/react-hooks": "^2.0.3", 24 | "@types/jest": "^24.0.18", 25 | "@types/node": "^12.7.12", 26 | "@types/react": "^16.9.5", 27 | "@types/react-dom": "^16.9.1", 28 | "@typescript-eslint/eslint-plugin": "^2.3.3", 29 | "@typescript-eslint/parser": "^2.3.3", 30 | "eslint": "^6.5.1", 31 | "eslint-config-prettier": "^6.4.0", 32 | "eslint-plugin-jest": "^22.17.0", 33 | "eslint-plugin-prettier": "^3.1.1", 34 | "eslint-plugin-react": "^7.16.0", 35 | "husky": "^3.0.8", 36 | "jest": "^24.9.0", 37 | "lint-staged": "^9.4.2", 38 | "npm-run-all": "^4.1.5", 39 | "prettier": "^1.18.2", 40 | "prettier-eslint": "^9.0.0", 41 | "react": "^16.10.2", 42 | "react-dom": "^16.10.2", 43 | "react-test-renderer": "^16.10.2", 44 | "rimraf": "^3.0.0", 45 | "ts-jest": "^24.1.0", 46 | "typescript": "^3.6.3", 47 | "uglify-js": "^3.6.1" 48 | }, 49 | "peerDependencies": { 50 | "react": "^16.10.2" 51 | }, 52 | "husky": { 53 | "hooks": { 54 | "pre-commit": "tsc --noEmit && lint-staged" 55 | } 56 | }, 57 | "lint-staged": { 58 | "src/**/*.{tsx,js}": [ 59 | "prettier --write", 60 | "eslint --fix", 61 | "git add" 62 | ] 63 | }, 64 | "directories": { 65 | "lib": "lib", 66 | "test": "__tests__" 67 | }, 68 | "files": [ 69 | "lib" 70 | ], 71 | "publishConfig": { 72 | "access": "public" 73 | }, 74 | "repository": { 75 | "type": "git", 76 | "url": "git+https://github.com/cshooks/hooks.git" 77 | }, 78 | "keywords": [ 79 | "react", 80 | "hooks", 81 | "trie", 82 | "cs" 83 | ], 84 | "bugs": { 85 | "url": "https://github.com/cshooks/hooks/issues" 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cshooks 2 | [![All Contributors](https://img.shields.io/badge/all_contributors-3-orange.svg?style=flat-square)](#contributors) 3 | [![Known Vulnerabilities](https://snyk.io/test/github/cshooks/hooks/badge.svg?targetFile=package.json)](https://snyk.io/test/github/cshooks/hooks?targetFile=package.json) 4 | 5 | A collection of React Hooks using Computer Science (CS) data structures & algorithms. 6 | 7 | # Purpose 8 | 9 | Mainly to learn CS data structure & algorithms 10 | _(and also implement'em in TypeScript)._ 11 | 12 | Hopefully some of the arcane data structures & algorithms help you as well. 13 | 14 | # Implemented 15 | 16 | ## useTrie 17 | Returns a Trie data structure, which is used to save a list of words to search in memory efficient manner & provide a type-ahead (not yet implemented) functionality. 18 | 19 | ## Contributors 20 | 21 | Thanks goes to these wonderful people ([emoji key](https://github.com/all-contributors/all-contributors#emoji-key)): 22 | 23 | 24 | 25 |
Sung M. Kim
Sung M. Kim

🚇 🔧 ⚠️ 📖 🤔 💻
Nick Taylor
Nick Taylor

👀 💻 🚇 ⚠️
Nicolas Marcora
Nicolas Marcora

🤔
26 | 27 | 28 | 29 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! -------------------------------------------------------------------------------- /packages/useHeap/test/index.test.ts: -------------------------------------------------------------------------------- 1 | import { renderHook, act } from '@testing-library/react-hooks'; 2 | import { cleanup } from '@testing-library/react' 3 | import { useMinHeap } from "../src"; 4 | 5 | // const log = console.log; 6 | 7 | describe("useMinHeap", () => { 8 | afterEach(cleanup); 9 | 10 | it("clears the heap", () => { 11 | const { result } = renderHook(() => useMinHeap([1, 2, 3])); 12 | const heap = result.current; 13 | 14 | act(() => { 15 | heap.clear(); 16 | }); 17 | 18 | act(() => { 19 | expect(heap.dump()).toEqual([]); 20 | }); 21 | }); 22 | 23 | it("returns undefined when heap is empty", () => { 24 | const { result } = renderHook(() => useMinHeap([])); 25 | const heap = result.current; 26 | 27 | act(() => { 28 | expect(heap.get()).toBeUndefined() 29 | expect(heap.peek()).toBeUndefined() 30 | }) 31 | }); 32 | 33 | it("peeks correctly without removing any items", () => { 34 | const input = [10, 2, 1, 99, 3]; 35 | const { result } = renderHook(() => useMinHeap(input)); 36 | const heap = result.current; 37 | 38 | act(() => { 39 | input.map(_ => expect(heap.peek()).toEqual(1)); 40 | }) 41 | }); 42 | 43 | it("gets minimum correctly", () => { 44 | const { result } = renderHook(() => useMinHeap([])); 45 | const heap = result.current; 46 | 47 | const input = [10, 2, 1, 99, 3, 5, 7]; 48 | // ascending order 49 | const expectedValues = [...input].sort((a, b) => a - b); 50 | 51 | input.map(value => act(() => heap.add(value))); 52 | // act(() => log(`minimum? heap.dump()`, heap.dump())); 53 | // log(`minimum? heap.dump()`, heap.dump()); 54 | expectedValues.forEach(expected => { 55 | act(() => { 56 | const actual = heap.get(); 57 | expect(actual).toBe(expected) 58 | }) 59 | }); 60 | 61 | 62 | act(() => { 63 | // Nothing else to pop, so the result should be undefined 64 | expect(heap.get()).toBeUndefined() 65 | }) 66 | }) 67 | }); 68 | 69 | it("renders hook correctly", () => { 70 | const { result } = renderHook(() => useMinHeap([])); 71 | expect(result.current).not.toBeNull(); 72 | }); 73 | 74 | it("adds all correctly", () => { 75 | const { result } = renderHook(() => useMinHeap([])); 76 | const heap = result.current; 77 | 78 | act(() => { 79 | [9, 8, 6, 5, 3, 1].forEach(heap.add); 80 | }); 81 | 82 | act(() => { 83 | expect(heap.dump()).toEqual([1, 5, 3, 9, 6, 8]); 84 | }) 85 | }); 86 | 87 | // prettier-ignore 88 | it("adds correctly", () => { 89 | const { result } = renderHook(() => useMinHeap([])); 90 | const heap = result.current; 91 | 92 | act(() => { heap.add(1); }); 93 | act(() => { 94 | expect(heap.dump()).toEqual([1]); 95 | }) 96 | act(() => { heap.add(3); }); 97 | act(() => { 98 | expect(heap.dump()).toEqual([1, 3]); 99 | }) 100 | act(() => { heap.add(5); }); 101 | act(() => { 102 | expect(heap.dump()).toEqual([1, 3, 5]); 103 | }) 104 | act(() => { heap.add(6); }); 105 | act(() => { 106 | expect(heap.dump()).toEqual([1, 3, 5, 6]); 107 | }) 108 | act(() => { heap.add(8); }); 109 | act(() => { 110 | expect(heap.dump()).toEqual([1, 3, 5, 6, 8]); 111 | }) 112 | act(() => { heap.add(9); }); 113 | act(() => { 114 | expect(heap.dump()).toEqual([1, 3, 5, 6, 8, 9]); 115 | }) 116 | }); 117 | 118 | it("dumps correctly", () => { 119 | const values = [1, 3, 5, 6, 8, 9]; 120 | const { result } = renderHook(() => useMinHeap(values)); 121 | 122 | act(() => { 123 | expect(result.current.dump()).toEqual([1, 3, 5, 6, 8, 9]); 124 | }) 125 | }); 126 | -------------------------------------------------------------------------------- /packages/useHeap/src/index.ts: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | // const log = console.log; 4 | 5 | type Value = number; 6 | const enum ActionType { 7 | Add = "ADD", 8 | Set = "SET", 9 | Remove = "REMOVE", 10 | Get = "GET", 11 | Clear = "Clear" 12 | } 13 | 14 | type Action = 15 | | { type: ActionType.Add; payload: { value: Value } } 16 | | { type: ActionType.Set; payload: { values: Value[] } } 17 | | { type: ActionType.Get } 18 | | { type: ActionType.Clear }; 19 | 20 | // In-line swap: https://stackoverflow.com/a/16201730/4035 21 | function swap(values: Value[], i1: number, i2: number): Value[] { 22 | // log(` swap Before => ${values}, i1=${i1}, i2=${i2}`); 23 | // values[i2] = [values[i1], (values[i1] = values[i2])][0]; 24 | // log(` swap AFTER => ${values}, i1=${i1}, i2=${i2}`); 25 | // 0, 1, 2, 3, 4, 5, 6 26 | // values=[10,20,30,40,50,60,70], i1=1 (20), i2=4(50) then 27 | // [10], [?], [30, 40], [?], [60, 70] 28 | 29 | const left = values.slice(0, i1); 30 | const middle = values.slice(i1 + 1, i2); 31 | const right = values.slice(i2 + 1); 32 | 33 | // log( 34 | // `values=${values}, i1=${i1}, i2=${i2}, left, values[i2], middle, values[i1], right`, 35 | // left, 36 | // values[i2], 37 | // middle, 38 | // values[i1], 39 | // right 40 | // ); 41 | 42 | return [...left, values[i2], ...middle, values[i1], ...right]; 43 | } 44 | 45 | const getParentIndex = (childIndex: number): number => ~~((childIndex - 1) / 2); 46 | const hasParent = (childIndex: number): boolean => 47 | getParentIndex(childIndex) >= 0; 48 | const getParent = (values: Value[], childIndex: number): Value => 49 | values[getParentIndex(childIndex)]; 50 | 51 | // type GetChildIndex = (parentIndex: number) => number; 52 | // const hasChild = (parentIndex: number, size: number): boolean => ( 53 | // getChildIndex: GetChildIndex 54 | // ) => getChildIndex(parentIndex) < size; 55 | 56 | const getRightChildIndex = (parentIndex: number) => parentIndex * 2 + 2; 57 | const hasRightChild = (parentIndex: number, size: number): boolean => 58 | getRightChildIndex(parentIndex) < size; 59 | const getRightChild = (values: Value[], parentIndex: number) => 60 | values[getRightChildIndex(parentIndex)]; 61 | 62 | const getLeftChildIndex = (parentIndex: number) => parentIndex * 2 + 1; 63 | const hasLeftChild = (parentIndex: number, size: number): boolean => 64 | getLeftChildIndex(parentIndex) < size; 65 | const getLeftChild = (values: Value[], parentIndex: number) => 66 | values[getLeftChildIndex(parentIndex)]; 67 | 68 | /** 69 | * Heapify the last item 70 | * @param {Value[]} values Values to re-heapify 71 | * @returns {Value[]} A valid MinHeap 72 | */ 73 | function heapifyDown(values: Value[]): Value[] { 74 | let index = 0; 75 | // let copy = [...values]; 76 | // Move the last item to the top and trickle down 77 | let lastIndex = values.length - 1; 78 | let copy = [values[lastIndex], ...values.slice(0, lastIndex)]; 79 | const size = copy.length; 80 | 81 | while (hasLeftChild(index, copy.length)) { 82 | let smallerChildIndex = getLeftChildIndex(index); 83 | // log( 84 | // `index=${index} size=${size} smallerChildIndex=${smallerChildIndex}, 85 | // getRightChildIndex=${getRightChildIndex(index)} 86 | // getLeftChildIndex=${getLeftChildIndex(index)} 87 | // hasRightChild()=${hasRightChild(index, size)}, 88 | // getRightChild()=${getRightChild(copy, size)}, 89 | // getLeftChild()=${getLeftChild(copy, size)}` 90 | // ); 91 | if ( 92 | hasRightChild(index, size) && 93 | getRightChild(copy, index) < getLeftChild(copy, index) 94 | ) { 95 | smallerChildIndex = getRightChildIndex(index); 96 | // log(`Right child is smaller!`); 97 | } 98 | 99 | if (copy[index] < copy[smallerChildIndex]) break; 100 | 101 | copy = swap(copy, index, smallerChildIndex); 102 | index = smallerChildIndex; 103 | } 104 | 105 | return copy; 106 | } 107 | 108 | function heapifyUp(values: Value[]) { 109 | let heapedValues = [...values]; 110 | let index = heapedValues.length - 1; 111 | 112 | // log( 113 | // `index=${index}, 114 | // getParentIndex(index)=${getParentIndex(index)}, 115 | // hasParent(index)=${hasParent(index)}, 116 | // getParent(heapedValues, index)=${getParent(heapedValues, index)}, 117 | // heapedValues[index]=${heapedValues[index]}`, 118 | // heapedValues 119 | // ); 120 | 121 | while ( 122 | hasParent(index) && 123 | getParent(heapedValues, index) > heapedValues[index] 124 | ) { 125 | // log(`while curr=${heapedValues[index]} index=${index}`); 126 | const parentIndex = getParentIndex(index); 127 | heapedValues = swap(heapedValues, parentIndex, index); 128 | index = parentIndex; 129 | } 130 | 131 | // log(` heapifyup result ===>`, heapedValues); 132 | return heapedValues; 133 | } 134 | 135 | function addValue(values: Value[], value: Value) { 136 | return heapifyUp([...values, value]); 137 | } 138 | 139 | function reducer(state: Value[], action: Action): Value[] { 140 | switch (action.type) { 141 | case ActionType.Add: 142 | const addValues = addValue(state, action.payload.value); 143 | return [...addValues]; 144 | case ActionType.Set: 145 | const setValues = heapifyDown(action.payload.values); 146 | return [...setValues]; 147 | case ActionType.Clear: 148 | return []; 149 | case ActionType.Get: 150 | default: 151 | return state; 152 | } 153 | } 154 | 155 | type HeapValue = Value | undefined; 156 | 157 | function useMinHeap(initialValues: Value[] = []) { 158 | const [values, dispatch] = React.useReducer( 159 | reducer, 160 | initialValues, 161 | initializer 162 | ); 163 | const freshValues = React.useRef(values); 164 | freshValues.current = values; 165 | 166 | function initializer(values: Value[]): Value[] { 167 | return values.reduce((acc, value) => addValue(acc, value), [] as Value[]); 168 | } 169 | 170 | function dump() { 171 | return freshValues.current; 172 | } 173 | 174 | function add(value: Value) { 175 | dispatch({ type: ActionType.Add, payload: { value } }); 176 | } 177 | 178 | function get(): HeapValue { 179 | const minimumValue = freshValues.current[0]; 180 | 181 | // We pop the first value and the current values is set to everything after the first item 182 | dispatch({ 183 | type: ActionType.Set, 184 | payload: { values: freshValues.current.slice(1) } 185 | }); 186 | 187 | return minimumValue; 188 | } 189 | 190 | function peek(): HeapValue { 191 | return freshValues.current[0]; 192 | } 193 | 194 | function clear(): void { 195 | dispatch({ type: ActionType.Clear }); 196 | } 197 | 198 | return { dump, add, get, peek, clear }; 199 | } 200 | 201 | export { useMinHeap }; 202 | -------------------------------------------------------------------------------- /packages/useTrie/src/index.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | // https://dev.to/nickytonline/comment/9j7l 3 | // https://www.geeksforgeeks.org/trie-insert-and-search/ 4 | 5 | interface ITrieNode { 6 | id: Word | undefined; 7 | next: Children; 8 | } 9 | 10 | type Children = Record; 11 | type TextSelector = (obj: any) => string; 12 | 13 | type Word = string | object; 14 | interface ITrie { 15 | has: (word: string, exactSearch?: boolean) => boolean; 16 | add: (word: Word, getText?: TextSelector) => void; 17 | remove: (word: string) => void; 18 | isEmpty: () => boolean; 19 | search: (word: string) => Word[]; 20 | } 21 | 22 | type TrieActionType = 'ADD' | 'REMOVE'; 23 | interface TrieAction { 24 | type: TrieActionType; 25 | payload: { 26 | word: Word; 27 | trie: ITrie; 28 | }; 29 | } 30 | 31 | type ReducerState = { 32 | trie: ITrie; 33 | word: string | Word; 34 | }; 35 | 36 | class TrieNode implements ITrieNode { 37 | id: Word | undefined; 38 | next: Children = {}; 39 | 40 | constructor(public character = '') {} 41 | } 42 | 43 | class Trie implements ITrie { 44 | private root: ITrieNode; 45 | 46 | constructor( 47 | words: Word[] = [], 48 | private isCaseInsensitive = true, 49 | private getText: TextSelector = obj => obj 50 | ) { 51 | this.root = new TrieNode(''); 52 | this.isCaseInsensitive = isCaseInsensitive; 53 | this.buildTrie(words); 54 | } 55 | 56 | private buildTrie(words: Word[]): void { 57 | words.forEach(word => this.add(word, this.getText)); 58 | } 59 | 60 | private normalizeWord(word: Word): string { 61 | return this.isCaseInsensitive 62 | ? (typeof word === 'string' 63 | ? word 64 | : this.getText(word) || '' 65 | ).toLowerCase() 66 | : typeof word === 'string' 67 | ? word 68 | : this.getText(word); 69 | } 70 | 71 | /* 72 | * @param {string} word A word to check if it exists in the trie 73 | * @param {boolean} exactSearch Return true only if the exact word is stored 74 | */ 75 | has = (wordToSearch: string, exactSearch: boolean = true): boolean => { 76 | let word = this.normalizeWord(wordToSearch); 77 | if (word === '') return false; 78 | 79 | let head = this.root as ITrieNode; 80 | for (let i = 0; i < word.length; i++) { 81 | const c = word[i]; 82 | if (!head.next[c]) { 83 | return false; 84 | } 85 | head = head.next[c]; 86 | } 87 | 88 | // If "id" at the current node exists, then it's a word 89 | return exactSearch ? !!head.id : true; 90 | }; 91 | 92 | add = (wordToAdd: Word, getText: TextSelector = obj => obj): void => { 93 | this.getText = this.getText || getText; 94 | 95 | let word = this.normalizeWord(wordToAdd); 96 | if (this.has(word)) return; 97 | 98 | let head: ITrieNode = this.root; 99 | for (let i = 0; i < word.length; i++) { 100 | const c = word[i]; 101 | if (!head.next[c]) { 102 | head.next[c] = new TrieNode(c); 103 | } 104 | head = head.next[c]; 105 | } 106 | 107 | head.id = wordToAdd; 108 | }; 109 | 110 | // https://www.geeksforgeeks.org/trie-delete/ 111 | remove = (wordToRemove: string): void => { 112 | let word = this.normalizeWord(wordToRemove); 113 | if (this.isEmpty() || !this.has(word)) return; 114 | 115 | this.root = this._remove(this.root, word); 116 | }; 117 | 118 | private _remove(node: ITrieNode, word: string, depth = 0): ITrieNode { 119 | if (!node) return new TrieNode(); 120 | 121 | if (depth === word.length) { 122 | if (this._isEmpty(node)) { 123 | return new TrieNode(); 124 | } else { 125 | delete node.id; 126 | return node; 127 | } 128 | } 129 | 130 | const c = word[depth]; 131 | node.next[c] = this._remove(node.next[c], word, depth + 1); 132 | if (this._isEmpty(node.next[c]) && !node.next[c].id) { 133 | delete node.next[c]; 134 | return node; 135 | } 136 | 137 | return node; 138 | } 139 | 140 | private _isEmpty(root: ITrieNode = this.root): boolean { 141 | return Object.keys(root.next).length === 0; 142 | } 143 | isEmpty = (): boolean => { 144 | return this._isEmpty(this.root); 145 | }; 146 | 147 | private isLastNode(node: ITrieNode): boolean { 148 | return Object.keys(node.next).length === 0; 149 | } 150 | 151 | private traverseToChildren( 152 | node: ITrieNode, 153 | word: string, 154 | depth: number 155 | ): ITrieNode { 156 | const c = word[depth]; 157 | const exactSearch = false; 158 | 159 | if (!this.has(word, exactSearch)) return new TrieNode(); 160 | if (depth === word.length - 1 && node.next[c]) return node.next[c]; 161 | 162 | return this.traverseToChildren(node.next[c], word, depth + 1); 163 | } 164 | 165 | // https://www.geeksforgeeks.org/auto-complete-feature-using-trie/ 166 | search = (wordToSearch: string): Word[] => { 167 | const word = this.normalizeWord(wordToSearch); 168 | const children = this.traverseToChildren(this.root, word, 0); 169 | 170 | const acc: Word[] = []; 171 | // initially prefix === '' because the we are passing a tree with one root. 172 | // the Root contains the last letter in the search term 173 | this.searchChildren(children, '', word, word.length, acc); 174 | return acc; 175 | }; 176 | 177 | private searchChildren( 178 | root: ITrieNode, 179 | prefix: string, 180 | word: string, 181 | totalDepth: number, 182 | acc: Word[] 183 | ): Word[] { 184 | if (!!root.id) acc.push(root.id); 185 | if (this.isLastNode(root)) return []; 186 | 187 | Object.keys(root.next).reduce( 188 | (words, c) => [ 189 | ...words, 190 | ...this.searchChildren(root.next[c], prefix + c, word, totalDepth, acc), 191 | ], 192 | [] as Word[] 193 | ); 194 | 195 | return acc; 196 | } 197 | } 198 | 199 | function reducer(state: ReducerState, action: TrieAction): ReducerState { 200 | switch (action.type) { 201 | case 'ADD': 202 | state.trie.add(action.payload.word); 203 | return { ...state, trie: state.trie }; 204 | case 'REMOVE': 205 | state.trie.remove(action.payload.word as string); 206 | return { ...state, trie: state.trie }; 207 | default: 208 | return state; 209 | } 210 | } 211 | 212 | /* 213 | * Build a trie for an efficient string search 214 | * @param initialWords: string[] List of words to build 215 | * @param isCaseInsensitive: bool "Their" & "their" are different 216 | * @param getText: TextSelector returns a text from an object to be added 217 | */ 218 | function useTrie( 219 | initialWords: Word[], 220 | isCaseInsensitive = true, 221 | getText: TextSelector = obj => obj 222 | ): ITrie { 223 | const trie = new Trie(initialWords, isCaseInsensitive, getText); 224 | const [state, dispatch] = React.useReducer(reducer, { trie, word: '' }); 225 | 226 | function add(word: Word): void { 227 | dispatch({ type: 'ADD', payload: { trie, word } }); 228 | } 229 | 230 | function remove(word: string): void { 231 | dispatch({ type: 'REMOVE', payload: { trie, word } }); 232 | } 233 | 234 | return { ...state.trie, add, remove }; 235 | } 236 | 237 | export { ITrie, Trie, Word }; 238 | export default useTrie; 239 | -------------------------------------------------------------------------------- /packages/useHeap/.rts2_cache_esm/rpt2_6c5aa84371fb4d0b540893c7ed8ea53c9d61186b/code/cache/78a2aba1afee055cc773ba110f69e3165fa9282a: -------------------------------------------------------------------------------- 1 | {"code":"import React from \"react\";\r\n// In-line swap: https://stackoverflow.com/a/16201730/4035\r\nfunction swap(values, i1, i2) {\r\n // log(` swap Before => ${values}, i1=${i1}, i2=${i2}`);\r\n // values[i2] = [values[i1], (values[i1] = values[i2])][0];\r\n // log(` swap AFTER => ${values}, i1=${i1}, i2=${i2}`);\r\n // 0, 1, 2, 3, 4, 5, 6\r\n // values=[10,20,30,40,50,60,70], i1=1 (20), i2=4(50) then\r\n // [10], [?], [30, 40], [?], [60, 70]\r\n const left = values.slice(0, i1);\r\n const middle = values.slice(i1 + 1, i2);\r\n const right = values.slice(i2 + 1);\r\n // log(\r\n // `values=${values}, i1=${i1}, i2=${i2}, left, values[i2], middle, values[i1], right`,\r\n // left,\r\n // values[i2],\r\n // middle,\r\n // values[i1],\r\n // right\r\n // );\r\n return [...left, values[i2], ...middle, values[i1], ...right];\r\n}\r\nconst getParentIndex = (childIndex) => ~~((childIndex - 1) / 2);\r\nconst hasParent = (childIndex) => getParentIndex(childIndex) >= 0;\r\nconst getParent = (values, childIndex) => values[getParentIndex(childIndex)];\r\n// type GetChildIndex = (parentIndex: number) => number;\r\n// const hasChild = (parentIndex: number, size: number): boolean => (\r\n// getChildIndex: GetChildIndex\r\n// ) => getChildIndex(parentIndex) < size;\r\nconst getRightChildIndex = (parentIndex) => parentIndex * 2 + 2;\r\nconst hasRightChild = (parentIndex, size) => getRightChildIndex(parentIndex) < size;\r\nconst getRightChild = (values, parentIndex) => values[getRightChildIndex(parentIndex)];\r\nconst getLeftChildIndex = (parentIndex) => parentIndex * 2 + 1;\r\nconst hasLeftChild = (parentIndex, size) => getLeftChildIndex(parentIndex) < size;\r\nconst getLeftChild = (values, parentIndex) => values[getLeftChildIndex(parentIndex)];\r\n/**\r\n * Heapify the last item\r\n * @param {Value[]} values Values to re-heapify\r\n * @returns {Value[]} A valid MinHeap\r\n */\r\nfunction heapifyDown(values) {\r\n let index = 0;\r\n // let copy = [...values];\r\n // Move the last item to the top and trickle down\r\n let lastIndex = values.length - 1;\r\n let copy = [values[lastIndex], ...values.slice(0, lastIndex)];\r\n const size = copy.length;\r\n while (hasLeftChild(index, copy.length)) {\r\n let smallerChildIndex = getLeftChildIndex(index);\r\n // log(\r\n // `index=${index} size=${size} smallerChildIndex=${smallerChildIndex},\r\n // getRightChildIndex=${getRightChildIndex(index)}\r\n // getLeftChildIndex=${getLeftChildIndex(index)}\r\n // hasRightChild()=${hasRightChild(index, size)},\r\n // getRightChild()=${getRightChild(copy, size)},\r\n // getLeftChild()=${getLeftChild(copy, size)}`\r\n // );\r\n if (hasRightChild(index, size) &&\r\n getRightChild(copy, index) < getLeftChild(copy, index)) {\r\n smallerChildIndex = getRightChildIndex(index);\r\n // log(`Right child is smaller!`);\r\n }\r\n if (copy[index] < copy[smallerChildIndex])\r\n break;\r\n copy = swap(copy, index, smallerChildIndex);\r\n index = smallerChildIndex;\r\n }\r\n return copy;\r\n}\r\nfunction heapifyUp(values) {\r\n let heapedValues = [...values];\r\n let index = heapedValues.length - 1;\r\n // log(\r\n // `index=${index},\r\n // getParentIndex(index)=${getParentIndex(index)},\r\n // hasParent(index)=${hasParent(index)},\r\n // getParent(heapedValues, index)=${getParent(heapedValues, index)},\r\n // heapedValues[index]=${heapedValues[index]}`,\r\n // heapedValues\r\n // );\r\n while (hasParent(index) &&\r\n getParent(heapedValues, index) > heapedValues[index]) {\r\n // log(`while curr=${heapedValues[index]} index=${index}`);\r\n const parentIndex = getParentIndex(index);\r\n heapedValues = swap(heapedValues, parentIndex, index);\r\n index = parentIndex;\r\n }\r\n // log(` heapifyup result ===>`, heapedValues);\r\n return heapedValues;\r\n}\r\nfunction addValue(values, value) {\r\n return heapifyUp([...values, value]);\r\n}\r\nfunction reducer(state, action) {\r\n switch (action.type) {\r\n case \"ADD\" /* Add */:\r\n const addValues = addValue(state, action.payload.value);\r\n return [...addValues];\r\n case \"SET\" /* Set */:\r\n const setValues = heapifyDown(action.payload.values);\r\n return [...setValues];\r\n case \"Clear\" /* Clear */:\r\n return [];\r\n case \"GET\" /* Get */:\r\n default:\r\n return state;\r\n }\r\n}\r\nfunction useMinHeap(initialValues = []) {\r\n const [values, dispatch] = React.useReducer(reducer, initialValues, initializer);\r\n const freshValues = React.useRef(values);\r\n freshValues.current = values;\r\n function initializer(values) {\r\n return values.reduce((acc, value) => addValue(acc, value), []);\r\n }\r\n function dump() {\r\n return freshValues.current;\r\n }\r\n function add(value) {\r\n dispatch({ type: \"ADD\" /* Add */, payload: { value } });\r\n }\r\n function get() {\r\n const minimumValue = freshValues.current[0];\r\n // We pop the first value and the current values is set to everything after the first item\r\n dispatch({\r\n type: \"SET\" /* Set */,\r\n payload: { values: freshValues.current.slice(1) }\r\n });\r\n return minimumValue;\r\n }\r\n function peek() {\r\n return freshValues.current[0];\r\n }\r\n function clear() {\r\n dispatch({ type: \"Clear\" /* Clear */ });\r\n }\r\n return { dump, add, get, peek, clear };\r\n}\r\nexport { useMinHeap };\r\n//# sourceMappingURL=index.js.map","references":["C:/misc/src/github/cshooks/hooks/packages/useHeap/node_modules/@types/react/index.d.ts"],"map":"{\"version\":3,\"file\":\"index.js\",\"sourceRoot\":\"\",\"sources\":[\"../../../src/index.ts\"],\"names\":[],\"mappings\":\"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\"}","dts":{"name":"C:/misc/src/github/cshooks/hooks/packages/useHeap/src/index.d.ts","writeByteOrderMark":false,"text":"declare type Value = number;\r\ndeclare function useMinHeap(initialValues?: Value[]): {\r\n dump: () => number[];\r\n add: (value: number) => void;\r\n get: () => number | undefined;\r\n peek: () => number | undefined;\r\n clear: () => void;\r\n};\r\nexport { useMinHeap };\r\n"}} 2 | -------------------------------------------------------------------------------- /packages/useTrie/README.md: -------------------------------------------------------------------------------- 1 | # `@cshooks/useTrie` 2 | 3 | ![npm](https://img.shields.io/npm/v/@cshooks/usetrie.svg) 4 | ![npm bundle size](https://img.shields.io/bundlephobia/min/@cshooks/usetrie.svg) 5 | ![npm bundle size](https://img.shields.io/bundlephobia/minzip/@cshooks/usetrie.svg) 6 | [![Known Vulnerabilities](https://snyk.io/test/github/cshooks/hooks/badge.svg?targetFile=packages%2FuseTrie%2Fpackage.json)](https://snyk.io/test/github/cshooks/hooks?targetFile=packages%2FuseTrie%2Fpackage.json) 7 | 8 | A React Hook that returns a [Trie](https://en.wikipedia.org/wiki/Trie), which enables a fast text match ([Typeahead](https://en.wikipedia.org/wiki/Typeahead)). 9 | 10 | # NPM Package 11 | 12 | https://www.npmjs.com/package/@cshooks/usetrie 13 | 14 | # Installation 15 | 16 | ```bash 17 | npm install @cshooks/usetrie 18 | ``` 19 | 20 | _Note for beginners_: 21 | 22 | - Don't install `@cshooks/usetrie` as a `devDependency` ([--save-dev](https://docs.npmjs.com/cli/install) flag for `npm install`) as it won't be included in your final build and will get an error complaining that the package cannot be found. 23 | - This library has a [peer dependency](https://nodejs.org/en/blog/npm/peer-dependencies/) for React version `^16.8.0`: This library needs to have React version 16.8.0 & up required. 24 | 25 | # Usage 26 | 27 | You can initialize the trie with either an array of 28 | 29 | 1. strings 30 | 1. objects 31 | 32 | ## 1. Initializing the trie with an array of `strings` 33 | 34 | Pass an array of string to the `useTrie` hook and optionally specify the case sensitivity 35 | (**default**: `isCaseInsensitive = true`) 36 | 37 | ```jsx 38 | import useTrie from '@cshooks/usetrie'; 39 | 40 | function App() { 41 | const words = ['abcd', 'abce', 'ABC', 'THE', 'their', 'there']; 42 | const isCaseInsensitive = true; 43 | const trie = useTrie(words, isCaseInsensitive); 44 | 45 | // or initialize and add/remove words later on 46 | // It's case-INsensitive" by default 47 | const trie = useTrie(); 48 | 49 | return
...
; 50 | } 51 | ``` 52 | 53 | ## 2. Initializing the trie with an array of `objects` 54 | 55 | **Note** that you need to pass the text selector method to let `useTrie` know how to extract a text object from your object (refer to the 3rd parameter, `textSelector`). 56 | 57 | ```jsx 58 | import useTrie from '@cshooks/usetrie'; 59 | 60 | function App() { 61 | const words = [ 62 | { id: 1, text: 'a' metadata: "1 - a"}, 63 | { id: 2, text: 'dog' metadata: "2 - dog"}, 64 | { id: 3, text: 'cat' metadata: "3 - cat"}, 65 | { id: 4, text: 'hel' metadata: "4 - hel"}, 66 | { id: 5, text: 'hell' metadata: "5 - hell"}, 67 | { id: 6, text: 'hello' metadata: "6 - hello"}, 68 | { id: 7, text: 'help' metadata: "7 - help"}, 69 | { id: 8, text: 'helping' metadata: "8 - helping"}, 70 | { id: 9, text: 'helps' metadata: "9 - helps"}, 71 | ]; 72 | const isCaseInsensitive = true; 73 | const textSelector = row => row.text; 74 | const trie = useTrie(words, isCaseInsensitive, textSelector); 75 | // or just pass a lambda (anonymous function) 76 | const trie = useTrie(words, isCaseInsensitive, o => o.text); 77 | 78 | return
...
; 79 | } 80 | ``` 81 | 82 | When you add a new "word" object to the trie, if you had already initialize the hook with a text selector method, then there is no need to specify it again. 83 | 84 | ```js 85 | const trie = useTrie(words, isCaseInsensitive, o => o.text); 86 | // ... elsewhere in the code 87 | trie.add({ id: 99, text: 'large text here' }); 88 | ``` 89 | 90 | If you have already initialized the hook with the text selector but specify it again in the `add`, then the text selector passed in the `add` overwrites the one specified during the initialization. 91 | 92 | ```js 93 | const trie = useTrie(words, isCaseInsensitive, o => o.text); 94 | // ... elsewhere in the code 95 | trie.add({ id: 999, title: 'Title to search' }, o => o.title); 96 | // No need to specify the text selector in subsequent "add" calls. 97 | trie.add({ id: 123, title: 'Next title to search' }); 98 | ``` 99 | 100 | When you add/remove an item in the trie, a new instance of trie is returned, 101 | so you can monitor on the `trie` dep when searching for a word. 102 | 103 | ```js 104 | const getMatches = React.useCallback(() => { 105 | return trie.search(state.term).map(word =>
  • {word}
  • ); 106 | }, [trie]); 107 | ``` 108 | 109 | # Demo 110 | 111 | ![simple demo](cshooks-simple-demo.gif) 112 | 113 | [![Edit @cshooks/usetrie demo - simple](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/3jzy58wqq) 114 | 115 |
    116 | A Simple demo Source 117 | 118 | ```ts 119 | import * as React from 'react'; 120 | import { render } from 'react-dom'; 121 | 122 | import useTrie, { Trie } from '@cshooks/usetrie'; 123 | import styled, { createGlobalStyle } from 'styled-components'; 124 | 125 | import './styles.css'; 126 | 127 | const log = console.log; 128 | 129 | const ContentContainer = styled.section` 130 | display: grid; 131 | grid: 1fr / 2fr 3fr; 132 | margin-top: 2rem; 133 | `; 134 | 135 | function reducer(state, action) { 136 | switch (action.type) { 137 | case 'SET_WORD': 138 | return { ...state, word: action.word }; 139 | case 'ADD_WORD': 140 | // Mutating the trie returns a new instance 141 | state.trie.add(action.word); 142 | return { ...state, words: [...state.words, action.word] }; 143 | case 'REMOVE_WORD': 144 | const removed = state.words.filter(word => word !== action.word); 145 | // Mutating the trie returns a new instance 146 | state.trie.remove(action.word); 147 | return { ...state, words: [...removed] }; 148 | case 'SET_TERM': 149 | return { ...state, term: action.term }; 150 | case 'SET_ISEXACT': { 151 | return { ...state, isExact: action.isExact }; 152 | } 153 | default: 154 | return state; 155 | } 156 | } 157 | 158 | function App() { 159 | // prettier-ignore 160 | const initialWords = [ 161 | "abcd", "abce", "ABC", "THE", "their", 162 | "there", "hel", "hell", "hello", "help", 163 | "helping", "helps" 164 | ]; 165 | const isCaseInsensitive = false; 166 | const trie = useTrie(initialWords, isCaseInsensitive); 167 | 168 | const initialState = { 169 | words: initialWords, 170 | word: '', 171 | term: '', 172 | isExact: true, 173 | trie, 174 | }; 175 | const [state, dispatch] = React.useReducer(reducer, initialState); 176 | 177 | const checkIfTermExists = e => 178 | dispatch({ type: 'SET_TERM', term: e.target.value }); 179 | 180 | const removeWord = React.useCallback( 181 | (word: string) => { 182 | log(`removing "${word}"`); 183 | trie.remove(word); 184 | dispatch({ type: 'REMOVE_WORD', word }); 185 | }, 186 | [trie] 187 | ); 188 | 189 | const AvailableWords = React.useMemo( 190 | () => 191 | state.words.map(word => { 192 | return ( 193 |
  • 194 | {' '} 197 | {word} 198 |
  • 199 | ); 200 | }), 201 | [state.words] 202 | ); 203 | 204 | const setWord = React.useCallback( 205 | e => dispatch({ type: 'SET_WORD', word: e.target.value }), 206 | [state.word] 207 | ); 208 | 209 | const addWord = React.useCallback( 210 | e => { 211 | e.preventDefault(); 212 | 213 | dispatch({ type: 'ADD_WORD', word: state.word }); 214 | }, 215 | [state.word] 216 | ); 217 | 218 | const getMatches = React.useCallback(() => { 219 | return trie.search(state.term).map(word =>
  • {word}
  • ); 220 | }, [trie]); 221 | 222 | return ( 223 | 224 |
    225 |

    Case Insensitive search

    226 |
    227 |
    228 |
    229 | 234 | 235 |
    236 |
    237 | 238 |
    239 |

    Available for search

    240 |
      {AvailableWords}
    241 |
    242 |
    243 |
    244 |
    245 | 251 |
    252 | 262 |
    263 |
    264 | The term "{state.term}"{' '} 265 | {trie.has(state.term, state.isExact) ? 'exists' : 'does not exist!'} 266 |
    267 |
    268 |

    Possible Matches

    269 |
      {getMatches()}
    270 |
    {' '} 271 |
    272 |
    273 |
    274 | ); 275 | } 276 | 277 | const GlobalStyle = createGlobalStyle({ 278 | boxSizing: 'border-box', 279 | }); 280 | 281 | const rootElement = document.getElementById('root'); 282 | render( 283 | 284 | 285 | 286 | , 287 | rootElement 288 | ); 289 | ``` 290 | 291 |
    292 | 293 | ## For both String & Object arrays 294 | 295 | [![Edit @cshooks/usetrie demo](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/zz2mxlxzp) 296 | 297 | # API 298 | 299 | ## useTrie 300 | 301 | ```ts 302 | useTrie( 303 | initialWords: Word[], 304 | isCaseInsensitive = true, 305 | getText?: (obj: any) => string = obj => obj 306 | ): Trie 307 | ``` 308 | 309 | - `initialWords: Word[]`: An array of string or object to populate the trie with 310 | - `isCaseInsensitive: boolean`: Word comparison flag 311 | - Is "abc" === "ABC"? If `isCaseInsensitive === true`, then false else true 312 | - `getText?: (obj: any) => string = obj => obj`: "Text" selector when when dealing with an object Array 313 | - e.g.) When `[{id: 1, text: "text1"}, {id: 2, text: "text2"}]` is passed as `initialWords`, then `o => o.text` would use the `text` property as the text to store internally. 314 | 315 | ```ts 316 | /* 317 | Public types 318 | */ 319 | declare type TextSelector = (obj: any) => string; 320 | declare type Word = string | object; 321 | interface ITrie { 322 | has: (word: string, exactSearch?: boolean) => boolean; 323 | add: (word: Word, getText?: TextSelector) => void; 324 | remove: (word: string) => void; 325 | isEmpty: () => boolean; 326 | search: (word: string) => Word[]; 327 | } 328 | declare class Trie implements ITrie { 329 | constructor( 330 | words?: Word[], 331 | isCaseInsensitive?: boolean, 332 | getText?: TextSelector 333 | ); 334 | has: (wordToSearch: string, exactSearch?: boolean) => boolean; 335 | add: (wordToAdd: Word, getText?: TextSelector) => void; 336 | remove: (wordToRemove: string) => void; 337 | isEmpty: () => boolean; 338 | search: (wordToSearch: string) => Word[]; 339 | } 340 | declare function useTrie( 341 | initialWords: Word[], 342 | isCaseInsensitive?: boolean, 343 | getText?: TextSelector 344 | ): ITrie; 345 | export { ITrie, Trie, Word }; 346 | export default useTrie; 347 | ``` 348 | 349 | ## Trie 350 | 351 | - `search = (word: string): string[]` 352 | - Search for the "word" in the trie 353 | - & returns an array of possible matches else an empty array. 354 | - e.g.) If the trie has ["abYZ", "abcd", "abce"], `trie.search('abc')` will return `["abcd", "abce"]`. 355 | - `trie.search('none-existing-word')` will return `[]`. 356 | - `has = (word: string, exactSearch: boolean = true): boolean` 357 | - Check if the `word` exists in the trie 358 | - `word` - a word to search in trie 359 | - `exactSearch` - match the `word` exactly else does a fuzzy match 360 | - e.g.) If trie contains `['abc', 'xyz', 'zte']`, then `has('a')` returns true as `a` matches the prefix of `abc`. 361 | - `add: (wordToAdd: Word, getText?: (obj: any) => string) => void;` 362 | - Add the `word` to trie 363 | - If the `wordToAdd` is an object and not specified in the `useTrie`, then pass the `getText` callback to let `trie` know how to extract the text from the object. 364 | - This `getText` callback persists 365 | - `remove = (word: string): void` 366 | - Remove the `word` from trie 367 | - `isEmpty = (): boolean` 368 | - Check if the current trie contains any words in it or not 369 | 370 | ## Additional Note 371 | 372 | `useTrie` returns an instance of type, `ITrie` instead of `Trie` due to `Trie` exposing more properties than necessary (due to lack of my TypeScript proficiency 😅) 373 | -------------------------------------------------------------------------------- /packages/useTrie/__tests__/index.test.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { renderHook, act } from '@testing-library/react-hooks'; 4 | import { cleanup, } from '@testing-library/react' 5 | import useTrie, { Trie } from '../src/index'; 6 | 7 | describe('useTrie Hook tests', () => { 8 | afterEach(cleanup); 9 | 10 | const isCaseInsensitive = true; 11 | 12 | test('Returns a new Trie instance on "add/remove"', () => { 13 | const { result } = renderHook(() => useTrie([], isCaseInsensitive)); 14 | const trieHook = result.current; 15 | 16 | act(() => { 17 | trieHook.add('abc'); 18 | }); 19 | expect(trieHook).not.toBe(result.current); 20 | 21 | act(() => { 22 | trieHook.remove('abc'); 23 | }); 24 | expect(trieHook).not.toBe(result.current); 25 | }); 26 | }); 27 | 28 | describe('Object array tests', () => { 29 | const isCaseInsensitive = true; 30 | 31 | describe('Typeahead', () => { 32 | test('return an empty array when not found', () => { 33 | const words = [ 34 | { id: 1, text: 'a' }, 35 | { id: 2, text: 'dog' }, 36 | { id: 3, text: 'cat' }, 37 | { id: 4, text: 'hel' }, 38 | { id: 5, text: 'hell' }, 39 | { id: 6, text: 'hello' }, 40 | { id: 7, text: 'help' }, 41 | { id: 8, text: 'helping' }, 42 | { id: 9, text: 'helps' }, 43 | ]; 44 | 45 | const textSelector = (row: any) => row.text; 46 | let trie = new Trie(words, isCaseInsensitive, textSelector); 47 | 48 | expect(trie.search('')).toEqual([]); 49 | expect(trie.search('xyz')).toEqual([]); 50 | expect(trie.search('cay')).toEqual([]); 51 | expect(trie.search('helllo')).toEqual([]); 52 | expect(trie.search('helpss')).toEqual([]); 53 | }); 54 | 55 | test('cannot find a removed record', () => { 56 | const words = [ 57 | { id: 1, meta: '1 - a', text: 'a' }, 58 | { id: 2, meta: '2 - dog', text: 'dog' }, 59 | { id: 3, meta: '3 - cat', text: 'cat' }, 60 | ]; 61 | 62 | const textSelector = (row: any) => row.text; 63 | const trie = new Trie(words, isCaseInsensitive, textSelector); 64 | 65 | expect(trie.search('a')).toEqual([{ id: 1, meta: '1 - a', text: 'a' }]); 66 | trie.remove('a'); 67 | expect(trie.search('a')).toEqual([]); 68 | 69 | expect(trie.search('dog')).toEqual([ 70 | { id: 2, meta: '2 - dog', text: 'dog' }, 71 | ]); 72 | trie.remove('dog'); 73 | expect(trie.search('dog')).toEqual([]); 74 | 75 | expect(trie.search('cat')).toEqual([ 76 | { id: 3, meta: '3 - cat', text: 'cat' }, 77 | ]); 78 | trie.remove('cat'); 79 | expect(trie.search('cat')).toEqual([]); 80 | 81 | expect(trie.isEmpty()).toBe(true); 82 | }); 83 | 84 | test('can find a newly added record', () => { 85 | const words = [ 86 | { id: 1, meta: '1 - a', text: 'a' }, 87 | { id: 2, meta: '2 - dog', text: 'dog' }, 88 | { id: 3, meta: '3 - cat', text: 'cat' }, 89 | ]; 90 | 91 | const textSelector = (row: any) => row.text; 92 | const trie = new Trie(words, isCaseInsensitive, textSelector); 93 | 94 | trie.add({ id: 4, meta: '4 - hel', text: 'hel' }); 95 | // Passing a custom text selector should work too 96 | trie.add({ id: 5, meta: '5 - hell', text: 'hell' }, o => o.text); 97 | trie.add({ id: 6, meta: '6 - hello', text: 'hello' }); 98 | 99 | expect(trie.search('hel')).toEqual([ 100 | { id: 4, meta: '4 - hel', text: 'hel' }, 101 | { id: 5, meta: '5 - hell', text: 'hell' }, 102 | { id: 6, meta: '6 - hello', text: 'hello' }, 103 | ]); 104 | expect(trie.search('hell')).toEqual([ 105 | { id: 5, meta: '5 - hell', text: 'hell' }, 106 | { id: 6, meta: '6 - hello', text: 'hello' }, 107 | ]); 108 | expect(trie.search('hello')).toEqual([ 109 | { id: 6, meta: '6 - hello', text: 'hello' }, 110 | ]); 111 | }); 112 | 113 | // prettier-ignore 114 | test('Happy Path', () => { 115 | const words2 = [ 116 | { id: 1, text: 'abcd', meta: '1 - abcd' }, 117 | { id: 2, text: 'abce', meta: '2 - abce' }, 118 | { id: 3, text: 'ABC', meta: '3 - ABC' }, 119 | { id: 4, text: 'THE', meta: '4 - THE' }, 120 | { id: 5, text: 'their', meta: '5 - their' }, 121 | { id: 6, text: 'there', meta: '6 - there' }, 122 | ]; 123 | 124 | const textSelector = (row: any) => row.text; 125 | const trie2 = new Trie(words2, isCaseInsensitive, textSelector); 126 | 127 | const expectedForA = [ 128 | { id: 3, text: 'ABC', meta: '3 - ABC' }, 129 | { id: 1, text: 'abcd', meta: '1 - abcd' }, 130 | { id: 2, text: 'abce', meta: '2 - abce' }, 131 | ].sort(); 132 | expect(trie2.search('a')).toEqual(expectedForA); 133 | expect(trie2.search('ab')).toEqual(expectedForA); 134 | expect(trie2.search('abc')).toEqual(expectedForA); 135 | 136 | expect(trie2.search('abcd')).toEqual([{ id: 1, text: 'abcd', meta: '1 - abcd' }]); 137 | expect(trie2.search('abce')).toEqual([{ id: 2, text: 'abce', meta: '2 - abce' }]); 138 | 139 | const expectedForT = [ 140 | { id: 4, text: 'THE', meta: '4 - THE' }, 141 | { id: 5, text: 'their', meta: '5 - their' }, 142 | { id: 6, text: 'there', meta: '6 - there' }, 143 | ]; 144 | expect(trie2.search('t')).toEqual(expectedForT); 145 | expect(trie2.search('th')).toEqual(expectedForT); 146 | expect(trie2.search('the')).toEqual(expectedForT); 147 | expect(trie2.search('thei')).toEqual([{ id: 5, text: 'their', meta: '5 - their' }]); 148 | expect(trie2.search('their')).toEqual([{ id: 5, text: 'their', meta: '5 - their' }]); 149 | expect(trie2.search('ther')).toEqual([{ id: 6, text: 'there', meta: '6 - there' }]); 150 | expect(trie2.search('there')).toEqual([{ id: 6, text: 'there', meta: '6 - there' }]); 151 | }); 152 | }); 153 | 154 | describe('Case Insensitive Tests', () => { 155 | const isCaseInsensitive = true; 156 | 157 | test('Trie has an exact search term', () => { 158 | const words1 = [ 159 | { key: 1, title: 'AbC', meta: 'title - AbC' }, 160 | { key: 2, title: 'aBd', meta: 'title - aBd' }, 161 | ]; 162 | 163 | const trie = new Trie(words1, isCaseInsensitive, o => o.title); 164 | 165 | [ 166 | 'aBc', 167 | 'aBC', 168 | 'ABC', 169 | 'abc', 170 | 'AbC', 171 | 'AbD', 172 | 'aBD', 173 | 'ABD', 174 | 'abd', 175 | 'aBd', 176 | ].forEach(word => { 177 | expect(trie.has(word)).toBe(true); 178 | }); 179 | 180 | expect(trie.has('')).toBe(false); 181 | 182 | const words2 = [ 183 | { key: 1, body: 'abcd', title: '1 - abcd' }, 184 | { key: 2, body: 'abce', title: '2 - abce' }, 185 | { key: 3, body: 'ABC', title: '3 - ABC' }, 186 | { key: 4, body: 'THE', title: '4 - THE' }, 187 | { key: 5, body: 'their', title: '5 - their' }, 188 | { key: 6, body: 'there', title: '6 - there' }, 189 | ]; 190 | 191 | const trie2 = new Trie(words2, isCaseInsensitive, o => o.body); 192 | 193 | ['ABCD', 'ABCD', 'ABCE', 'abc', 'the', 'THEIR', 'THERE'].forEach(word => { 194 | expect(trie2.has(word)).toBe(true); 195 | }); 196 | }); 197 | 198 | test('Trie has a fuzz search term', () => { 199 | const words1 = [ 200 | { key: 1, title: 'abcd', meta: 'title - abcd' }, 201 | { key: 2, title: 'abda', meta: 'title - abda' }, 202 | ]; 203 | 204 | const trie = new Trie(words1, isCaseInsensitive, o => o.title); 205 | 206 | [ 207 | 'a', 208 | 'A', 209 | 'ab', 210 | 'aB', 211 | 'abc', 212 | 'aBc', 213 | 'aBC', 214 | 'ABC', 215 | 'abcd', 216 | 'Abcd', 217 | 'ABcd', 218 | 'ABCd', 219 | 'ABCD', 220 | ].forEach(word => { 221 | expect(trie.has(word, false)).toBe(true); 222 | }); 223 | 224 | ['ay', 'aby', 'abcy', 'abcy', 'b', 'c', 'd', ''].forEach(word => { 225 | expect(trie.has(word, false)).toBe(false); 226 | }); 227 | }); 228 | 229 | test('Add new objects and confirm that it exists', () => { 230 | const trie = new Trie([], isCaseInsensitive, o => o.title); 231 | 232 | trie.add({ key: 1, title: 'abc', meta: 'title - abc' }); 233 | trie.add({ key: 2, title: 'abd', meta: 'title - abd' }); 234 | 235 | expect(trie.has('abc')).toBe(true); 236 | expect(trie.has('abd')).toBe(true); 237 | expect(trie.has('abx')).toBe(false); 238 | expect(trie.has('dex')).toBe(false); 239 | expect(trie.has('')).toBe(false); 240 | expect(trie.has('a', false)).toBe(true); 241 | expect(trie.has('d', false)).toBe(false); 242 | expect(trie.has('', false)).toBe(false); 243 | }); 244 | 245 | test('Remove terms and confirm that it does not exist', () => { 246 | const words1 = [ 247 | { key: 1, title: 'abcd', meta: 'title - abcd' }, 248 | { key: 2, title: 'abce', meta: 'title - abce' }, 249 | ]; 250 | 251 | const trie = new Trie(words1, isCaseInsensitive, o => o.title); 252 | 253 | // As the case is "in"-sensitive, removing capitalized word should work too. 254 | trie.remove('ABCD'); 255 | expect(trie.has('abcd')).toBe(false); 256 | expect(trie.has('a', false)).toBe(true); 257 | expect(trie.has('ab', false)).toBe(true); 258 | expect(trie.has('abc', false)).toBe(true); 259 | expect(trie.has('abce', false)).toBe(true); 260 | expect(trie.has('abce')).toBe(true); 261 | 262 | trie.remove('abce'); 263 | expect(trie.has('abce')).toBe(false); 264 | expect(trie.isEmpty()).toBe(true); 265 | 266 | trie.remove('abcd'); 267 | expect(trie.has('abcd')).toBe(false); 268 | expect(trie.has('abda')).toBe(false); 269 | }); 270 | 271 | test('Check if trie is empty or not', () => { 272 | const trie = new Trie([], isCaseInsensitive, o => o.title); 273 | expect(trie.isEmpty()).toBe(true); 274 | 275 | trie.add({ key: 1, title: 'abcd', meta: 'title - abcd' }); 276 | expect(trie.isEmpty()).toBe(false); 277 | 278 | trie.remove('abcd'); 279 | expect(trie.isEmpty()).toBe(true); 280 | }); 281 | }); 282 | 283 | describe('Case Sensitive Tests', () => { 284 | const isCaseInsensitive = false; 285 | 286 | test('Trie has an exact search term', () => { 287 | const words1 = [ 288 | { key: 1, title: 'AbC', meta: 'title - AbC' }, 289 | { key: 2, title: 'aBd', meta: 'title - aBd' }, 290 | ]; 291 | 292 | const trie = new Trie(words1, isCaseInsensitive, o => o.title); 293 | expect(trie.has('AbC')).toBe(true); 294 | expect(trie.has('abc')).toBe(false); 295 | expect(trie.has('aBd')).toBe(true); 296 | expect(trie.has('ABD')).toBe(false); 297 | expect(trie.has('abx')).toBe(false); 298 | expect(trie.has('dex')).toBe(false); 299 | expect(trie.has('')).toBe(false); 300 | }); 301 | 302 | test('Trie has a fuzz search term', () => { 303 | const words1 = [ 304 | { key: 1, title: 'abcd', meta: 'title - abcd' }, 305 | { key: 2, title: 'abda', meta: 'title - abda' }, 306 | ]; 307 | 308 | const trie = new Trie(words1, isCaseInsensitive, o => o.title); 309 | expect(trie.has('a', false)).toBe(true); 310 | expect(trie.has('d', false)).toBe(false); 311 | expect(trie.has('', false)).toBe(false); 312 | }); 313 | 314 | test('Add new objects and confirm that it exists', () => { 315 | const trie = new Trie([], isCaseInsensitive, o => o.title); 316 | 317 | trie.add({ key: 1, title: 'abc', meta: 'title - abc' }); 318 | trie.add({ key: 2, title: 'abd', meta: 'title - abd' }); 319 | 320 | expect(trie.has('abc')).toBe(true); 321 | expect(trie.has('abd')).toBe(true); 322 | expect(trie.has('abx')).toBe(false); 323 | expect(trie.has('dex')).toBe(false); 324 | expect(trie.has('')).toBe(false); 325 | expect(trie.has('a', false)).toBe(true); 326 | expect(trie.has('d', false)).toBe(false); 327 | expect(trie.has('', false)).toBe(false); 328 | }); 329 | 330 | test('Remove terms and confirm that it does not exist', () => { 331 | const words1 = [ 332 | { key: 1, title: 'abcd', meta: 'title - abcd' }, 333 | { key: 2, title: 'abce', meta: 'title - abce' }, 334 | ]; 335 | 336 | const trie = new Trie(words1, isCaseInsensitive, o => o.title); 337 | 338 | trie.remove('abcd'); 339 | expect(trie.has('abcd')).toBe(false); 340 | expect(trie.has('a', false)).toBe(true); 341 | expect(trie.has('ab', false)).toBe(true); 342 | expect(trie.has('abc', false)).toBe(true); 343 | expect(trie.has('abce', false)).toBe(true); 344 | expect(trie.has('abce')).toBe(true); 345 | 346 | trie.remove('abce'); 347 | expect(trie.has('abce')).toBe(false); 348 | expect(trie.isEmpty()).toBe(true); 349 | 350 | trie.remove('abcd'); 351 | expect(trie.has('abcd')).toBe(false); 352 | expect(trie.has('abda')).toBe(false); 353 | }); 354 | 355 | test('Check if trie is empty or not', () => { 356 | const trie = new Trie([], isCaseInsensitive, o => o.title); 357 | expect(trie.isEmpty()).toBe(true); 358 | 359 | trie.add({ key: 999, title: 'ab' }); 360 | expect(trie.isEmpty()).toBe(false); 361 | 362 | trie.remove('ab'); 363 | expect(trie.isEmpty()).toBe(true); 364 | }); 365 | }); 366 | }); 367 | 368 | describe('String array tests', () => { 369 | describe('Typeahead', () => { 370 | const isCaseInsensitive = true; 371 | 372 | test('return an empty array when not found', () => { 373 | const words = ['a1234', 'def']; 374 | const trie = new Trie(words, isCaseInsensitive); 375 | 376 | const result = trie.search('xyz'); 377 | expect(result).toEqual([]); 378 | }); 379 | 380 | test('cannot find a removed record', () => { 381 | const trie = new Trie(['abc', 'def'], isCaseInsensitive); 382 | expect(trie.search('abc')).toEqual(['abc']); 383 | expect(trie.search('def')).toEqual(['def']); 384 | 385 | trie.remove('abc'); 386 | expect(trie.search('abc')).toEqual([]); 387 | 388 | trie.remove('def'); 389 | expect(trie.search('def')).toEqual([]); 390 | }); 391 | 392 | test('can find a newly added record', () => { 393 | const trie = new Trie(['abc'], isCaseInsensitive); 394 | expect(trie.search('abc')).toEqual(['abc']); 395 | expect(trie.search('xyz')).toEqual([]); 396 | 397 | trie.add('xyz'); 398 | expect(trie.search('xyz')).toEqual(['xyz']); 399 | 400 | trie.add('x'); 401 | trie.add('xy'); 402 | expect(trie.search('x')).toEqual(['x', 'xy', 'xyz']); 403 | expect(trie.search('xy')).toEqual(['xy', 'xyz']); 404 | }); 405 | 406 | test('Happy Path', () => { 407 | // prettier-ignore 408 | const words = ['a', 'dog', 'cat', 'hel', 'hell', 'hello', 'help', 'helping', 'helps']; 409 | const trie = new Trie(words, isCaseInsensitive); 410 | 411 | const result = trie.search('hel'); 412 | const expected = ['hel', 'hell', 'hello', 'help', 'helping', 'helps']; 413 | 414 | expect(result).toEqual(expected.sort()); 415 | 416 | const words2 = ['abcd', 'abce', 'ABC', 'THE', 'their', 'there']; 417 | const trie2 = new Trie(words2, isCaseInsensitive); 418 | 419 | expect(trie2.search('a')).toEqual(['abcd', 'abce', 'ABC'].sort()); 420 | expect(trie2.search('ab')).toEqual(['abcd', 'abce', 'ABC'].sort()); 421 | expect(trie2.search('abc')).toEqual(['abcd', 'abce', 'ABC'].sort()); 422 | expect(trie2.search('abcd')).toEqual(['abcd']); 423 | expect(trie2.search('abce')).toEqual(['abce']); 424 | expect(trie2.search('t')).toEqual(['THE', 'their', 'there'].sort()); 425 | expect(trie2.search('th')).toEqual(['THE', 'their', 'there'].sort()); 426 | expect(trie2.search('the')).toEqual(['THE', 'their', 'there'].sort()); 427 | expect(trie2.search('thei')).toEqual(['their']); 428 | expect(trie2.search('their')).toEqual(['their']); 429 | expect(trie2.search('ther')).toEqual(['there']); 430 | expect(trie2.search('there')).toEqual(['there']); 431 | }); 432 | }); 433 | 434 | describe('Case Insensitive Tests', () => { 435 | const isCaseInsensitive = true; 436 | 437 | test('Trie has an exact search term', () => { 438 | const trie = new Trie(['AbC', 'aBd'], isCaseInsensitive); 439 | 440 | [ 441 | 'aBc', 442 | 'aBC', 443 | 'ABC', 444 | 'abc', 445 | 'AbC', 446 | 'AbD', 447 | 'aBD', 448 | 'ABD', 449 | 'abd', 450 | 'aBd', 451 | ].forEach(word => { 452 | expect(trie.has(word)).toBe(true); 453 | }); 454 | 455 | expect(trie.has('')).toBe(false); 456 | 457 | const words = ['abcd', 'abce', 'ABC', 'THE', 'their', 'there']; 458 | const trie2 = new Trie(words, isCaseInsensitive); 459 | 460 | ['ABCD', 'ABCE', 'abc', 'the', 'THEIR', 'THERE'].forEach(word => { 461 | expect(trie2.has(word)).toBe(true); 462 | }); 463 | }); 464 | 465 | test('Trie has a fuzz search term', () => { 466 | const trie = new Trie(['abcd', 'abda'], isCaseInsensitive); 467 | 468 | [ 469 | 'a', 470 | 'A', 471 | 'ab', 472 | 'aB', 473 | 'abc', 474 | 'aBc', 475 | 'aBC', 476 | 'ABC', 477 | 'abcd', 478 | 'Abcd', 479 | 'ABcd', 480 | 'ABCd', 481 | 'ABCD', 482 | ].forEach(word => { 483 | expect(trie.has(word, false)).toBe(true); 484 | }); 485 | 486 | ['ay', 'aby', 'abcy', 'abcy', 'b', 'c', 'd', ''].forEach(word => { 487 | expect(trie.has(word, false)).toBe(false); 488 | }); 489 | }); 490 | 491 | test('Add new terms and confirm that it exists', () => { 492 | const trie = new Trie([], isCaseInsensitive); 493 | trie.add('abc'); 494 | trie.add('abd'); 495 | 496 | expect(trie.has('abc')).toBe(true); 497 | expect(trie.has('abd')).toBe(true); 498 | expect(trie.has('abx')).toBe(false); 499 | expect(trie.has('dex')).toBe(false); 500 | expect(trie.has('')).toBe(false); 501 | expect(trie.has('a', false)).toBe(true); 502 | expect(trie.has('d', false)).toBe(false); 503 | expect(trie.has('', false)).toBe(false); 504 | }); 505 | 506 | test('Trying to delete from an empty trie should not fail', () => { 507 | const trie = new Trie([], isCaseInsensitive); 508 | expect(() => trie.remove('aaa')).not.toThrow(); 509 | }); 510 | 511 | test('Remove terms and confirm that it does not exist', () => { 512 | const trie = new Trie(['abcd', 'abce'], isCaseInsensitive); 513 | // As the case is "in"-sensitive, removing capitalized word should work too. 514 | trie.remove('ABCD'); 515 | expect(trie.has('abcd')).toBe(false); 516 | expect(trie.has('a', false)).toBe(true); 517 | expect(trie.has('ab', false)).toBe(true); 518 | expect(trie.has('abc', false)).toBe(true); 519 | expect(trie.has('abce', false)).toBe(true); 520 | expect(trie.has('abce')).toBe(true); 521 | 522 | trie.remove('abce'); 523 | expect(trie.has('abce')).toBe(false); 524 | expect(trie.isEmpty()).toBe(true); 525 | 526 | trie.remove('abcd'); 527 | expect(trie.has('abcd')).toBe(false); 528 | expect(trie.has('abda')).toBe(false); 529 | }); 530 | 531 | test('Check if trie is empty or not', () => { 532 | const trie = new Trie([], isCaseInsensitive); 533 | expect(trie.isEmpty()).toBe(true); 534 | 535 | trie.add('ab'); 536 | expect(trie.isEmpty()).toBe(false); 537 | 538 | trie.remove('ab'); 539 | expect(trie.isEmpty()).toBe(true); 540 | }); 541 | }); 542 | 543 | describe('Case Sensitive Tests', () => { 544 | const isCaseInsensitive = true; 545 | 546 | test('Trie has an exact search term', () => { 547 | const trie = new Trie(['abc', 'abd'], isCaseInsensitive); 548 | expect(trie.has('abc')).toBe(true); 549 | expect(trie.has('abd')).toBe(true); 550 | expect(trie.has('abx')).toBe(false); 551 | expect(trie.has('dex')).toBe(false); 552 | expect(trie.has('')).toBe(false); 553 | }); 554 | 555 | test('Trie has a fuzz search term', () => { 556 | const trie = new Trie(['abcd', 'abda'], isCaseInsensitive); 557 | expect(trie.has('a', false)).toBe(true); 558 | expect(trie.has('d', false)).toBe(false); 559 | expect(trie.has('', false)).toBe(false); 560 | }); 561 | 562 | test('Add new terms and confirm that it exists', () => { 563 | const trie = new Trie([], isCaseInsensitive); 564 | trie.add('abc'); 565 | trie.add('abd'); 566 | 567 | expect(trie.has('abc')).toBe(true); 568 | expect(trie.has('abd')).toBe(true); 569 | expect(trie.has('abx')).toBe(false); 570 | expect(trie.has('dex')).toBe(false); 571 | expect(trie.has('')).toBe(false); 572 | expect(trie.has('a', false)).toBe(true); 573 | expect(trie.has('d', false)).toBe(false); 574 | expect(trie.has('', false)).toBe(false); 575 | }); 576 | 577 | test('Trying to delete from an empty trie should not fail', () => { 578 | const trie = new Trie([], isCaseInsensitive); 579 | expect(() => trie.remove('aaa')).not.toThrow(); 580 | }); 581 | 582 | test('Remove terms and confirm that it does not exist', () => { 583 | const trie = new Trie(['abcd', 'abce'], isCaseInsensitive); 584 | trie.remove('abcd'); 585 | expect(trie.has('abcd')).toBe(false); 586 | expect(trie.has('a', false)).toBe(true); 587 | expect(trie.has('ab', false)).toBe(true); 588 | expect(trie.has('abc', false)).toBe(true); 589 | expect(trie.has('abce', false)).toBe(true); 590 | expect(trie.has('abce')).toBe(true); 591 | 592 | trie.remove('abce'); 593 | expect(trie.has('abce')).toBe(false); 594 | expect(trie.isEmpty()).toBe(true); 595 | 596 | trie.remove('abcd'); 597 | expect(trie.has('abcd')).toBe(false); 598 | expect(trie.has('abda')).toBe(false); 599 | }); 600 | 601 | test('Check if trie is empty or not', () => { 602 | const trie = new Trie([], isCaseInsensitive); 603 | expect(trie.isEmpty()).toBe(true); 604 | 605 | trie.add('ab'); 606 | expect(trie.isEmpty()).toBe(false); 607 | 608 | trie.remove('ab'); 609 | expect(trie.isEmpty()).toBe(true); 610 | }); 611 | }); 612 | }); 613 | --------------------------------------------------------------------------------