├── .gitignore ├── example.js ├── package.json ├── README.md ├── LICENSE ├── test.js └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | const toRadixTree = require('./') 2 | 3 | const tree = toRadixTree([ 4 | 'hello', 5 | 'world', 6 | 'hello world' 7 | ]) 8 | 9 | require('util').inspect.defaultOptions.depth = Infinity 10 | console.log(tree) 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "to-radix-tree", 3 | "version": "1.0.0", 4 | "description": "Convert a series of strings into a radix tree", 5 | "main": "index.js", 6 | "dependencies": {}, 7 | "devDependencies": { 8 | "standard": "^12.0.1", 9 | "tape": "^4.9.1" 10 | }, 11 | "scripts": { 12 | "test": "standard && tape test.js" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/mafintosh/to-radix-tree.git" 17 | }, 18 | "author": "Mathias Buus (@mafintosh)", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/mafintosh/to-radix-tree/issues" 22 | }, 23 | "homepage": "https://github.com/mafintosh/to-radix-tree" 24 | } 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # to-radix-tree 2 | 3 | Convert a series of strings into a [radix tree](https://en.wikipedia.org/wiki/Radix_tree) 4 | 5 | ``` 6 | npm install to-radix-tree 7 | ``` 8 | 9 | ## Usage 10 | 11 | ``` js 12 | const toRadixTree = require('to-radix-tree') 13 | 14 | const tree = toRadixTree([ 15 | 'hello world', 16 | 'hello verden' 17 | ]) 18 | 19 | console.log(tree) 20 | ``` 21 | 22 | Running the above will print something like 23 | 24 | ```js 25 | { 26 | prefix: 'hello ', 27 | children: [{ 28 | prefix: 'world', 29 | value: 'hello world' 30 | }, { 31 | prefix: 'verden', 32 | value: 'hello verden' 33 | }] 34 | } 35 | ``` 36 | 37 | ## API 38 | 39 | #### `tree = toRadixTree(array)` 40 | 41 | Turns an array of strings into a radix tree. The tree looks like this 42 | 43 | ``` 44 | { 45 | prefix: 'all-children-start-with-this', 46 | children: [tree..], 47 | value: anArrayItemThatMatchAllParentPrefixes 48 | } 49 | ``` 50 | 51 | ## License 52 | 53 | MIT 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Mathias Buus 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | const tape = require('tape') 2 | const toRadixTree = require('./') 3 | 4 | tape('basic', function (t) { 5 | const tree1 = toRadixTree([ 6 | 'hello world' 7 | ]) 8 | 9 | t.same(tree1, { prefix: 'hello world', children: null, value: 'hello world' }) 10 | 11 | const tree2 = toRadixTree([ 12 | 'hello world', 13 | 'hello verden' 14 | ]) 15 | 16 | t.same(tree2, { 17 | prefix: 'hello ', 18 | children: [ 19 | { prefix: 'verden', children: null, value: 'hello verden' }, 20 | { prefix: 'world', children: null, value: 'hello world' } 21 | ], 22 | value: null 23 | }) 24 | 25 | const tree3 = toRadixTree([ 26 | 'hello world', 27 | 'hello verden', 28 | 'hello' 29 | ]) 30 | 31 | t.same(tree3, { 32 | prefix: 'hello', 33 | children: [ 34 | { prefix: '', children: null, value: 'hello' }, 35 | { 36 | prefix: ' ', 37 | children: [ 38 | { prefix: 'verden', children: null, value: 'hello verden' }, 39 | { prefix: 'world', children: null, value: 'hello world' } 40 | ], 41 | value: null 42 | } 43 | ], 44 | value: null 45 | }) 46 | 47 | t.end() 48 | }) 49 | 50 | tape('non strings', function (t) { 51 | const tree = toRadixTree([ 52 | 'hello world', 53 | [ 'hello ', world ] 54 | ]) 55 | 56 | t.same(tree, { 57 | prefix: 'hello ', 58 | children: [ 59 | { prefix: 'world', children: null, value: 'hello world' }, 60 | { prefix: [ world ], children: null, value: [ 'hello ', world ] } 61 | ], 62 | value: null 63 | }) 64 | 65 | t.end() 66 | 67 | function world () {} 68 | }) 69 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = buildTree 2 | 3 | function buildTree (strings) { 4 | strings = strings.map(normalize).sort(sort) 5 | if (!strings.length) return { match: [], next: null, value: null } 6 | return buildTreeRecursive(strings, 0, 0) 7 | } 8 | 9 | function buildTreeRecursive (strings, start, parent) { 10 | if (!strings.length) return { match: [], next: null, value: null } 11 | 12 | var i = 0 13 | var first = strings[i] 14 | 15 | if (strings.length === 1) return { prefix: first.match.slice(parent), children: null, value: first.value } 16 | 17 | const node = { 18 | prefix: first.match.slice(parent, start), 19 | children: [], 20 | value: null 21 | } 22 | 23 | while (i < strings.length) { 24 | const j = i 25 | first = strings[i++] 26 | var min = first.match.length 27 | for (; i < strings.length; i++) { 28 | const next = similar(first.match, strings[i].match, start) 29 | if (next === start) break 30 | min = Math.min(next, min) 31 | } 32 | node.children.push(buildTreeRecursive(strings.slice(j, i), min, start)) 33 | } 34 | 35 | if (node.children.length === 1) return node.children[0] 36 | return node 37 | } 38 | 39 | function toMatch (val) { 40 | if (isString(val)) return val 41 | if (Array.isArray(val)) return val 42 | return val.match 43 | } 44 | 45 | function normalize (value) { 46 | const str = toMatch(value) 47 | 48 | if (isString(str)) return { match: str, value } 49 | 50 | const res = [] 51 | for (var i = 0; i < str.length; i++) { 52 | if (isString(str[i])) normalizeString(str[i], res) 53 | else res.push(str[i]) 54 | } 55 | return { match: res, value } 56 | } 57 | 58 | function normalizeString (str, res) { 59 | for (var i = 0; i < str.length; i++) { 60 | res.push(str[i]) 61 | } 62 | } 63 | 64 | function similar (a, b, i) { 65 | const len = Math.min(a.length, b.length) 66 | for (; i < len; i++) { 67 | if (a[i] !== b[i]) return i 68 | } 69 | return len 70 | } 71 | 72 | function isString (s) { 73 | return typeof s === 'string' 74 | } 75 | 76 | function sort (a, b) { 77 | const len = Math.min(a.match.length, b.match.length) 78 | 79 | for (var i = 0; i < len; i++) { 80 | const ai = a.match[i] 81 | const bi = b.match[i] 82 | if (!isString(ai) && !isString(bi)) continue 83 | if (!isString(ai)) return 1 84 | if (!isString(bi)) return -1 85 | if (ai < bi) return -1 86 | if (ai > bi) return 1 87 | } 88 | 89 | return a.match.length - b.match.length 90 | } 91 | --------------------------------------------------------------------------------