wdqs-editor
Version:
Wikidata Query Service Editor
275 lines (252 loc) • 7.53 kB
JavaScript
/*
* TRIE implementation in Javascript
* Copyright (c) 2010 Saurabh Odhyan | http://odhyan.com
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* Date: Nov 7, 2010
*/
/*
* A trie, or prefix tree, is a multi-way tree structure useful for storing strings over an alphabet.
* It has been used to store large dictionaries of English (say) words in spell-checking programs
* and in natural-language "understanding" programs.
* @see http://en.wikipedia.org/wiki/Trie
* @see http://www.csse.monash.edu.au/~lloyd/tildeAlgDS/Tree/Trie/
/*
* @class Trie
* @constructor
*/
var Trie = module.exports = function() {
this.words = 0;
this.prefixes = 0;
this.children = [];
};
Trie.prototype = {
/*
* Insert a word into the dictionary.
* Recursively traverse through the trie nodes, and create new node if does not already exist.
*
* @method insert
* @param {String} str Word to insert in the dictionary
* @param {Integer} pos Current index of the string to be inserted
* @return {Void}
*/
insert: function(str, pos) {
if(str.length == 0) { //blank string cannot be inserted
return;
}
var T = this,
k,
child;
if(pos === undefined) {
pos = 0;
}
if(pos === str.length) {
T.words ++;
return;
}
T.prefixes ++;
k = str[pos];
if(T.children[k] === undefined) { //if node for this char doesn't exist, create one
T.children[k] = new Trie();
}
child = T.children[k];
child.insert(str, pos + 1);
},
/*
* Remove a word from the dictionary.
*
* @method remove
* @param {String} str Word to be removed
* @param {Integer} pos Current index of the string to be removed
* @return {Void}
*/
remove: function(str, pos) {
if(str.length == 0) {
return;
}
var T = this,
k,
child;
if(pos === undefined) {
pos = 0;
}
if(T === undefined) {
return;
}
if(pos === str.length) {
T.words --;
return;
}
T.prefixes --;
k = str[pos];
child = T.children[k];
child.remove(str, pos + 1);
},
/*
* Update an existing word in the dictionary.
* This method removes the old word from the dictionary and inserts the new word.
*
* @method update
* @param {String} strOld The old word to be replaced
* @param {String} strNew The new word to be inserted
* @return {Void}
*/
update: function(strOld, strNew) {
if(strOld.length == 0 || strNew.length == 0) {
return;
}
this.remove(strOld);
this.insert(strNew);
},
/*
* Count the number of times a given word has been inserted into the dictionary
*
* @method countWord
* @param {String} str Word to get count of
* @param {Integer} pos Current index of the given word
* @return {Integer} The number of times a given word exists in the dictionary
*/
countWord: function(str, pos) {
if(str.length == 0) {
return 0;
}
var T = this,
k,
child,
ret = 0;
if(pos === undefined) {
pos = 0;
}
if(pos === str.length) {
return T.words;
}
k = str[pos];
child = T.children[k];
if(child !== undefined) { //node exists
ret = child.countWord(str, pos + 1);
}
return ret;
},
/*
* Count the number of times a given prefix exists in the dictionary
*
* @method countPrefix
* @param {String} str Prefix to get count of
* @param {Integer} pos Current index of the given prefix
* @return {Integer} The number of times a given prefix exists in the dictionary
*/
countPrefix: function(str, pos) {
if(str.length == 0) {
return 0;
}
var T = this,
k,
child,
ret = 0;
if(pos === undefined) {
pos = 0;
}
if(pos === str.length) {
return T.prefixes;
}
var k = str[pos];
child = T.children[k];
if(child !== undefined) { //node exists
ret = child.countPrefix(str, pos + 1);
}
return ret;
},
/*
* Find a word in the dictionary
*
* @method find
* @param {String} str The word to find in the dictionary
* @return {Boolean} True if the word exists in the dictionary, else false
*/
find: function(str) {
if(str.length == 0) {
return false;
}
if(this.countWord(str) > 0) {
return true;
} else {
return false;
}
},
/*
* Get all words in the dictionary
*
* @method getAllWords
* @param {String} str Prefix of current word
* @return {Array} Array of words in the dictionary
*/
getAllWords: function(str) {
var T = this,
k,
child,
ret = [];
if(str === undefined) {
str = "";
}
if(T === undefined) {
return [];
}
if(T.words > 0) {
ret.push(str);
}
for(k in T.children) {
child = T.children[k];
ret = ret.concat(child.getAllWords(str + k));
}
return ret;
},
/*
* Autocomplete a given prefix
*
* @method autoComplete
* @param {String} str Prefix to be completed based on dictionary entries
* @param {Integer} pos Current index of the prefix
* @return {Array} Array of possible suggestions
*/
autoComplete: function(str, pos) {
var T = this,
k,
child;
if(str.length == 0) {
if (pos === undefined) {
return T.getAllWords(str);
} else {
return [];
}
}
if(pos === undefined) {
pos = 0;
}
k = str[pos];
child = T.children[k];
if(child === undefined) { //node doesn't exist
return [];
}
if(pos === str.length - 1) {
return child.getAllWords(str);
}
return child.autoComplete(str, pos + 1);
}
};