radix-compression
Version:
A prefix tree for compressing long strings
169 lines (155 loc) • 4.8 kB
JavaScript
// this is the main constructor function, each node takes a string
// as a value and has an array of children
var Radix = function(value){
this.children = [];
this.locations = [];
if(value === undefined){
this.value = '';
} else {
this.value = value.string;
this.locations.push(value.index);
}
};
// the insert function inserts a new string into the tree
Radix.prototype.insert = function(obj){
// if the string is already in the tree, return the node containing that string
if (this.value === obj.string) {
this.locations.push(obj.index);
}
var node, tst;
// otherwise iterate through the child nodes
for(var i = 0; i < this.children.length; i++) {
// store the current child and a regex containing its value
node = this.children[i];
var testString = node.value.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
tst = new RegExp('^'+testString+'.*');
// if the current child node's string is found in the input value, start over from that node
if( tst.test(obj.string) ){
var l = node.value.length;
// we only pass on the difference between our input and the current node's value
obj.string = obj.string.substring(l, obj.string.length);
return node.insert(obj);
}
}
// if we don't find that value itself or a node containing part of it,
// insert the string at the current node
this.children.push(new Radix(obj));
};
// this is a simple function that counts the number of characters stored in the entire
// tree, can be used to calculate compression rate.
Radix.prototype.countChars = function(){
var count = this.value.length;
if(this.children.length){
var node;
for(var i = 0; i < this.children.length; i++){
node = this.children[i];
count += node.countChars();
}
}
return count;
};
// check to see if a given string is in the
Radix.prototype.contains = function(target, current){
if (current === undefined) {
current = this.value;
}
var node, chk, newVal;
for(var i = 0; i < this.children.length; i++) {
node = this.children[i];
newVal = current + node.value;
if( newVal === target) { return true; }
chk = new RegExp('^'+newVal+'\.*');
if(chk.test(target)) {
return node.contains(target, newVal);
}
}
return false;
};
// this function constructs a list of all the words stored in the tree
Radix.prototype.reconstruct = function(value){
var result = [];
var search = function(context, value){
if (value !== undefined){
value = value + context.value;
} else {
value = context.value;
}
if (value.length){
for(var k = 0; k < context.locations.length; k++){
result[context.locations[k]] = value;
}
}
if (context.children.length){
var node;
for(var i = 0; i < context.children.length; i++){
node = context.children[i];
search(node, value);
}
}
}
search(this, value);
return result.join(' ');
};
// This is the classic autocomplete function. Given a partial word, the tree returns
// a list of all the words that can be completed with that partial.
Radix.prototype.complete = function(target, previous){
var result = [], current;
if(current === undefined){
current = this.value;
}
if(previous){
current = previous + this.value;
}
for(var i = 0; i < this.children.length; i++){
var node = this.children[i];
var chk = new RegExp('^'+current+node.value+'\.*');
var tst = new RegExp('^'+target+'\.*');
if(chk.test(target)){
return node.complete(target, current);
}
else if(tst.test(current+node.value)){
result = result.concat(node.reconstruct(current));
}
}
return result;
};
Radix.prototype.documentInsert = function(string){
var splitString = string.split(' ');
var array = [];
for(var k = 0; k < splitString.length; k ++) {
if(splitString[k].length > 0){
array.push({ string: splitString[k], index: k });
}
}
var sort = function(left, right) {
var result = [];
while(left.length && right.length){
if(left[0].string.length <= right[0].string.length){
result.push(left.shift());
} else {
result.push(right.shift());
}
}
while(left.length){
result.push(left.shift());
}
while(right.length){
result.push(right.shift());
}
return result;
};
var merge = function(array){
if(array.length < 2) {
return array;
}
var mid = Math.floor(array.length/2);
var left = array.slice(0, mid);
var right = array.slice(mid, array.length);
return sort(merge(left), merge(right));
};
var sorted = merge(array);
for(var i = 0; i < sorted.length; i++){
this.insert(sorted[i]);
}
};
exports.Radix = Radix;