trie-d
Version:
Minimal trie datastructure representation
300 lines (274 loc) • 8.7 kB
JavaScript
'use strict';
const CONSTANT = require('./src/constants/trie');
const ErrorClass = require('./src/errors/trie');
const Node = require('./src/model/node');
const _ = require('lodash');
const Error = new ErrorClass();
class Trie {
/**
* Props {delimiter}
* Delimiter supports String, Positive Integer and undefined.
* Positive Integer : delimits provided data by number of character count.
* String(s) : sets up as the that string or regex as the delimiter
* undefined : treats '' as delimiter.
* @param props = { delimiter = 'str' || 1 || undefined }
*/
constructor(props) {
let delimiter = _.get(props, 'delimiter');
if (delimiter) {
this.has_delimiter = true;
if (typeof delimiter === 'number') {
delimiter = parseInt(delimiter);
if (delimiter >= 1) {
this.delimeter_type = CONSTANT.COUNT_MATCH;
this.delimiter = delimiter;
} else {
Error.invalidDelimiter(delimiter);
}
} else {
this.delimiter = delimiter.toString();
this.delimeter_type = CONSTANT.STR_MATCH;
}
} else {
this.has_delimiter = false;
}
this.first_level_map = {};
}
_getFirstLevelMap() {
return this.first_level_map;
}
_hasDelimiter() {
return this.has_delimiter;
}
_getDelimiterType() {
return this.delimeter_type;
}
_getDelimiter() {
return this.delimiter;
}
_addValueToNode(node, values) {
if (_.isEmpty(values)) {
node.markWord();
node.increaseDependency();
return;
}
if (node.getNode(values[0])) {
return this._addValueToNode(node.getNode(values[0]), values.splice(1));
} else {
const newNode = new Node(values[0]);
node.setNode(newNode);
return this._addValueToNode(newNode, values.splice(1));
}
}
add(value) {
if (_.isEmpty(value)) {
return;
}
value = _.toLower(value);
let values = [];
let firstKey;
if (this._hasDelimiter()) {
const delimiterType = this._getDelimiterType();
const delimiter = this._getDelimiter();
if (delimiterType === CONSTANT.COUNT_MATCH) {
values = this._splitByCount(value, delimiter);
} else {
values = value.split(delimiter);
}
firstKey = values[0];
} else {
values = value.split('');
firstKey = values[0];
}
if (this._getFirstLevelMap()[firstKey]) {
const firstNode = this.first_level_map[firstKey];
return this._addValueToNode(firstNode, values.splice(1));
} else {
const newNode = new Node(firstKey);
this.first_level_map[newNode.getKey()] = newNode;
return this._addValueToNode(newNode, values.splice(1));
}
}
_getText(prefix, key) {
let delimiter = this._getDelimiter();
key = key || '';
if (delimiter) {
if (this._getDelimiterType() === CONSTANT.COUNT_MATCH) {
/**
* ignoring in case of count delimeter
*/
delimiter = '';
}
} else {
delimiter = '';
}
return _.trim(prefix + delimiter + key);
}
_getAllNextValues(result, node, prefix) {
if (node.isLeaf()) {
return result;
}
const keys = _.keys(node.getMap());
_.each(keys, key => {
if (node.getNode(key).getWordMark()) {
result.push(this._getText(prefix, key));
}
result = _.union(result, this._getAllNextValues(result, node.getNode(key), this._getText(prefix, key)));
});
return result;
}
_getNearMatch(result, node, values, prefix) {
if (_.isEmpty(values)) {
if (node.getWordMark()) {
result.push(this._getText(prefix));
}
return this._getAllNextValues(result, node, prefix);
}
const key = values[0];
const nextNode = node.getNode(key);
if (nextNode.isLeaf()) {
result.push(this._getText(prefix, key));
return result;
} else {
return this._getNearMatch(result, nextNode, values.splice(1), this._getText(prefix, key));
}
}
_removeValue(node, values) {
// console.log(JSON.stringify(node), values);
/* istanbul ignore else */
if (_.isEmpty(values)) {
/* istanbul ignore else */
if (node.getWordMark()) {
/* istanbul ignore else */
if (node.getDependency() === 1) {
node.unMarkWord();
}
return node.decreaseDependency();
}
}
const nextKey = values[0];
/* istanbul ignore else */
if (node.getNode(nextKey)) {
return this._removeValue(node.getNode(nextKey), values.splice(1));
}
}
nearMatch(value) {
if (_.isEmpty(value)) {
return [];
}
let values = [];
let firstKey;
if (this._hasDelimiter()) {
const delimiterType = this._getDelimiterType();
const delimiter = this._getDelimiter();
if (delimiterType === CONSTANT.COUNT_MATCH) {
values = this._splitByCount(value, delimiter);
} else {
values = value.split(delimiter);
}
firstKey = values[0];
} else {
values = value.split('');
firstKey = values[0];
}
if (this._getFirstLevelMap()[firstKey]) {
const result = this._getNearMatch([], this._getFirstLevelMap()[firstKey], values.splice(1), firstKey);
return result.sort(this._lexi);
} else {
return [];
}
}
/**
* expects array of strings to be searched in trie
* returns the result in order of entry of strings, Unique values.
* @param values
* @return {Array}
*/
nearMatchAll(values) {
if (_.isEmpty(values)) {
return [];
}
let result = [];
_.each(values, value => {
result = result.concat(this.nearMatch(value));
});
return _.uniq(result);
}
/**
* expects array of strings to be added to trie
* @param elements
*/
addAll(elements) {
if (_.isEmpty(elements)) {
return;
}
_.each(elements, ele => {
this.add(ele);
});
}
/**
* removes a particular string
* @param value
* @return {*}
*/
remove(value) {
if (_.isEmpty(value)) {
return;
}
value = _.toLower(value);
let values = [];
let firstKey;
if (this._hasDelimiter()) {
const delimiterType = this._getDelimiterType();
const delimiter = this._getDelimiter();
if (delimiterType === CONSTANT.COUNT_MATCH) {
values = this._splitByCount(value, delimiter);
} else {
values = value.split(delimiter);
}
firstKey = values[0];
} else {
values = value.split('');
firstKey = values[0];
}
/* istanbul ignore else */
if (this._getFirstLevelMap()[firstKey]) {
const firstNode = this.first_level_map[firstKey];
return this._removeValue(firstNode, values.splice(1));
}
return;
}
/**
*
* @param values
*/
removeAll(values) {
if (_.isEmpty(values)) {
return;
}
_.each(values, value => {
this.remove(value);
});
}
/** ***************************************************************
* utilities
*****************************************************************/
_splitByCount(str, count) {
if (_.isEmpty(str) || !count || count <= 0) {
return [];
}
const res = [];
let start = 0;
while (start < str.length) {
const temp = str.substr(start, count);
res.push(temp);
start += count;
}
return res;
}
_lexi(curr, next) {
return curr.toString().localeCompare(next);
}
}
module
.exports = Trie;