@linkdotnet/stringoperations
Version:
Collection of string utilities. Edit-Distances, Search and Data structures. Offers for example trie, levenshtein distance.
201 lines (200 loc) • 7.72 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Rope = void 0;
/**
* Represents a rope
*/
class Rope {
constructor() {
this.left = undefined;
this.right = undefined;
this.hasToRecaluclateWeights = false;
this.fragment = '';
this.weight = 0;
}
/**
* Returns the character at the given index
* @param index The zero-based index of the desired character.
* @returns Returns the character at the specified index.
*/
charAt(index) {
this.checkRecalculation();
return Rope.charAtInternal(this, index);
}
/**
* Represents the rope as a single string
* @returns The whole rope as string
*/
toString() {
const results = [];
Rope.getStrings(this, results);
return results.join('');
}
/**
* Concats a string to the rope and returns the new rope
* @param other Other string which will be appended to the rope
* @param recalculateWeights If set to true the weights of the new rope will be calculated immediately
* @returns New rope instance with both texts
* @remarks If concating a lot of strings, setting recalculateWeights to true is very expensive.
* Operations which require the calculated weight will check if a recalculation is needed.
* ```ts
* for (let i = 0; i < 10000; i++) {
* newRope = newRope.concat('test', false)
* }
* newRope.charAt(2) // This will automatically recalculate the weight
* ```
*/
concatString(other, recalculateWeights = false) {
const otherRope = Rope.create(other);
return this.concatRope(otherRope, recalculateWeights);
}
/**
* Concats a rope to the current one and returns the new combined rope
* @param other Other string which will be appended to the rope
* @param recalculateWeights If set to true the weights of the new rope will be calculated immediately
* @returns New rope instance with both texts
* @remarks If concating a lot of strings, setting recalculateWeights to true is very expensive.
* Operations which require the calculated weight will check if a recalculation is needed.
* ```ts
* for (let i = 0; i < 10000; i++) {
* newRope = newRope.concat('test', false)
* }
* newRope.charAt(2) // This will automatically recalculate the weight
* ```
*/
concatRope(other, recalculateWeights = false) {
const newRope = new Rope();
newRope.left = this;
newRope.right = other;
newRope.hasToRecaluclateWeights = true;
if (recalculateWeights) {
this.checkRecalculation();
}
return newRope;
}
/**
* Splits the rope into two new ones at the defined index
* @param index Zero based index where the rope should be split. The index is always part of the left side of the rope
*/
split(index) {
if (index < 0) {
throw new RangeError('Index was negative');
}
this.checkRecalculation();
return Rope.splitRope(this, index);
}
/**
* Inserts another rope into the current one and returns the merger
* @param rope New rope to add to the current one
* @param index Zero based index where the new rope has to be inserted
* @returns The merged rope
*/
insert(rope, index) {
const pair = this.split(index);
const left = pair[0].concatRope(rope);
return left.concatRope(pair[1]);
}
/**
* Inserts a string into the current rope and returns the merger
* @param rope New rope to add to the current one
* @param index Zero based index where the new rope has to be inserted
* @returns The merged rope
*/
insertString(text, index) {
return this.insert(Rope.create(text), index);
}
/**
* Deletes a substring from the rope
* @param startIndex Inclusive starting index
* @param length Length to delete
* @returns New rope with deleted range
*/
delete(startIndex, length) {
if (startIndex < 0) {
throw new RangeError('Index was negative');
}
if (length <= 0) {
throw new RangeError('Length has to be at least 1');
}
this.checkRecalculation();
const beforeStartIndex = this.split(startIndex - 1)[0];
const afterEndIndex = this.split(startIndex + length - 1)[1];
return beforeStartIndex.concatRope(afterEndIndex);
}
/**
* Creates the rope with the given text
* @param text The initial text to add in the rope
* @param leafLength Size of a single leaf. Every leaf is a substring of the given text
* @returns Instance of a rope
*/
static create(text, leafLength = 8) {
return this.createInternal(text, leafLength, 0, text.length - 1);
}
static createInternal(text, leafLength, leftIndex, rightIndex) {
const node = new Rope();
if (rightIndex - leftIndex > leafLength) {
const center = (rightIndex + leftIndex + 1) / 2;
node.left = Rope.createInternal(text, leafLength, leftIndex, center);
node.right = Rope.createInternal(text, leafLength, center + 1, rightIndex);
}
else {
node.fragment = text.slice(leftIndex, rightIndex + 1);
}
node.calculateAndSetWeight();
return node;
}
static getWeightInternal(node) {
if (node.left !== undefined && node.right !== undefined) {
return this.getWeightInternal(node.left) + this.getWeightInternal(node.right);
}
return node.left !== undefined ? this.getWeightInternal(node.left) : node.fragment.length;
}
static charAtInternal(node, index) {
if (node.weight <= index && node.right) {
return Rope.charAtInternal(node.right, index - node.weight);
}
if (node.left) {
return Rope.charAtInternal(node.left, index);
}
return node.fragment[index];
}
static getStrings(node, results) {
if (!node) {
return;
}
if (!node.left && !node.right) {
results.push(node.fragment);
}
Rope.getStrings(node.left, results);
Rope.getStrings(node.right, results);
}
static splitRope(node, index) {
if (!node.left) {
if (index === node.weight - 1) {
return [node, undefined];
}
const item1 = Rope.create(node.fragment.slice(0, index + 1));
const item2 = Rope.create(node.fragment.slice(index + 1, node.weight));
return [item1, item2];
}
if (index === node.weight - 1) {
return [node.left, node.right];
}
if (index < node.weight) {
const splitLeftSide = Rope.splitRope(node.left, index);
return [splitLeftSide[0], splitLeftSide[1].concatRope(node.right)];
}
const splitRightSide = Rope.splitRope(node.right, index - node.weight);
return [node.left.concatRope(splitRightSide[0]), splitRightSide[1]];
}
calculateAndSetWeight() {
this.weight = this.left === undefined ? this.fragment.length : Rope.getWeightInternal(this.left);
}
checkRecalculation() {
if (this.hasToRecaluclateWeights) {
this.calculateAndSetWeight();
this.hasToRecaluclateWeights = false;
}
}
}
exports.Rope = Rope;