UNPKG

@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
"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;