Compare commits

...

4 commits

Author SHA1 Message Date
Revone 69d78fdc87 release: v2.0.3 2025-03-01 18:53:15 +13:00
Revone 50e8d9f315 release: v2.0.3 2025-03-01 16:14:36 +13:00
Revone 6dfcc7603f styles: Simplify trampoline to a traditional approach. release: Publish version 2.0.2. 2025-03-01 15:58:43 +13:00
Revone c2dc5aa914 feat: Use Number.MAX_SAFE_INTEGER instead of Infinity. 2025-01-23 19:12:20 +13:00
12 changed files with 322 additions and 63 deletions

View file

@ -8,7 +8,7 @@ All notable changes to this project will be documented in this file.
- [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
- [`auto-changelog`](https://github.com/CookPete/auto-changelog)
## [v2.0.0](https://github.com/zrwusa/data-structure-typed/compare/v1.51.5...main) (upcoming)
## [v2.0.3](https://github.com/zrwusa/data-structure-typed/compare/v1.51.5...main) (upcoming)
### Changes

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "data-structure-typed",
"version": "2.0.0",
"version": "2.0.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "data-structure-typed",
"version": "2.0.0",
"version": "2.0.3",
"license": "MIT",
"devDependencies": {
"@eslint/compat": "^1.2.2",

View file

@ -1,7 +1,7 @@
{
"name": "data-structure-typed",
"version": "2.0.0",
"description": "Javascript Data Structure. Heap, Binary Tree, Red Black Tree, Linked List, Deque, Trie, HashMap, Directed Graph, Undirected Graph, Binary Search Tree(BST), AVL Tree, Priority Queue, Graph, Queue, Tree Multiset, Singly Linked List, Doubly Linked List, Max Heap, Max Priority Queue, Min Heap, Min Priority Queue, Stack. Benchmark compared with C++ STL. API aligned with ES6 and Java.util. Usability is comparable to Python",
"version": "2.0.3",
"description": "Standard data structure",
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
"browser": "dist/umd/data-structure-typed.min.js",

View file

@ -339,7 +339,7 @@ export abstract class AbstractGraph<
if (isWeight) {
const allPaths = this.getAllPathsBetween(v1, v2);
let min = Infinity;
let min = Number.MAX_SAFE_INTEGER;
for (const path of allPaths) {
min = Math.min(this.getPathSumWeight(path), min);
}
@ -404,7 +404,7 @@ export abstract class AbstractGraph<
if (isWeight) {
if (isDFS) {
const allPaths = this.getAllPathsBetween(v1, v2, 10000);
let min = Infinity;
let min = Number.MAX_SAFE_INTEGER;
let minIndex = -1;
let index = 0;
for (const path of allPaths) {
@ -475,7 +475,7 @@ export abstract class AbstractGraph<
getMinDist: boolean = false,
genPaths: boolean = false
): DijkstraResult<VO> {
let minDist = Infinity;
let minDist = Number.MAX_SAFE_INTEGER;
let minDest: VO | undefined = undefined;
let minPath: VO[] = [];
const paths: VO[][] = [];
@ -494,13 +494,13 @@ export abstract class AbstractGraph<
for (const vertex of vertexMap) {
const vertexOrKey = vertex[1];
if (vertexOrKey instanceof AbstractVertex) distMap.set(vertexOrKey, Infinity);
if (vertexOrKey instanceof AbstractVertex) distMap.set(vertexOrKey, Number.MAX_SAFE_INTEGER);
}
distMap.set(srcVertex, 0);
preMap.set(srcVertex, undefined);
const getMinOfNoSeen = () => {
let min = Infinity;
let min = Number.MAX_SAFE_INTEGER;
let minV: VO | undefined = undefined;
for (const [key, value] of distMap) {
if (!seen.has(key)) {
@ -537,7 +537,7 @@ export abstract class AbstractGraph<
seen.add(cur);
if (destVertex && destVertex === cur) {
if (getMinDist) {
minDist = distMap.get(destVertex) || Infinity;
minDist = distMap.get(destVertex) || Number.MAX_SAFE_INTEGER;
}
if (genPaths) {
getPaths(destVertex);
@ -605,7 +605,7 @@ export abstract class AbstractGraph<
getMinDist: boolean = false,
genPaths: boolean = false
): DijkstraResult<VO> {
let minDist = Infinity;
let minDist = Number.MAX_SAFE_INTEGER;
let minDest: VO | undefined = undefined;
let minPath: VO[] = [];
const paths: VO[][] = [];
@ -621,7 +621,7 @@ export abstract class AbstractGraph<
for (const vertex of vertexMap) {
const vertexOrKey = vertex[1];
if (vertexOrKey instanceof AbstractVertex) distMap.set(vertexOrKey, Infinity);
if (vertexOrKey instanceof AbstractVertex) distMap.set(vertexOrKey, Number.MAX_SAFE_INTEGER);
}
const heap = new Heap<{ key: number; value: VO }>([], { comparator: (a, b) => a.key - b.key });
@ -661,7 +661,7 @@ export abstract class AbstractGraph<
seen.add(cur);
if (destVertex && destVertex === cur) {
if (getMinDist) {
minDist = distMap.get(destVertex) || Infinity;
minDist = distMap.get(destVertex) || Number.MAX_SAFE_INTEGER;
}
if (genPaths) {
getPaths(destVertex);
@ -732,7 +732,7 @@ export abstract class AbstractGraph<
const paths: VO[][] = [];
const distMap: Map<VO, number> = new Map();
const preMap: Map<VO, VO> = new Map(); // predecessor
let min = Infinity;
let min = Number.MAX_SAFE_INTEGER;
let minPath: VO[] = [];
// TODO
let hasNegativeCycle: boolean | undefined;
@ -745,7 +745,7 @@ export abstract class AbstractGraph<
const numOfEdges = edgeMap.length;
this._vertexMap.forEach(vertex => {
distMap.set(vertex, Infinity);
distMap.set(vertex, Number.MAX_SAFE_INTEGER);
});
distMap.set(srcVertex, 0);
@ -759,7 +759,7 @@ export abstract class AbstractGraph<
const sWeight = distMap.get(s);
const dWeight = distMap.get(d);
if (sWeight !== undefined && dWeight !== undefined) {
if (distMap.get(s) !== Infinity && sWeight + weight < dWeight) {
if (distMap.get(s) !== Number.MAX_SAFE_INTEGER && sWeight + weight < dWeight) {
distMap.set(d, sWeight + weight);
if (genPath) preMap.set(d, s);
}
@ -804,7 +804,7 @@ export abstract class AbstractGraph<
const weight = edgeMap[j].weight;
const sWeight = distMap.get(s);
if (sWeight) {
if (sWeight !== Infinity && sWeight + weight < sWeight) hasNegativeCycle = true;
if (sWeight !== Number.MAX_SAFE_INTEGER && sWeight + weight < sWeight) hasNegativeCycle = true;
}
}
}
@ -860,7 +860,7 @@ export abstract class AbstractGraph<
for (let i = 0; i < n; i++) {
for (let j = 0; j < n; j++) {
costs[i][j] = this.getEdge(idAndVertices[i][1], idAndVertices[j][1])?.weight || Infinity;
costs[i][j] = this.getEdge(idAndVertices[i][1], idAndVertices[j][1])?.weight || Number.MAX_SAFE_INTEGER;
}
}

View file

@ -38,6 +38,72 @@ export class SinglyLinkedListNode<E = any> extends LinkedListNode<E> {
* 4. High Efficiency in Insertion and Deletion: Adding or removing elements in a linked list does not require moving other elements, making these operations more efficient than in arrays.
* Caution: Although our linked list classes provide methods such as at, setAt, addAt, and indexOf that are based on array indices, their time complexity, like that of the native Array.lastIndexOf, is 𝑂(𝑛). If you need to use these methods frequently, you might want to consider other data structures, such as Deque or Queue (designed for random access). Similarly, since the native Array.shift method has a time complexity of 𝑂(𝑛), using an array to simulate a queue can be inefficient. In such cases, you should use Queue or Deque, as these data structures leverage deferred array rearrangement, effectively reducing the average time complexity to 𝑂(1).
*
* @example
* // implementation of a basic text editor
* class TextEditor {
* private content: SinglyLinkedList<string>;
* private cursorIndex: number;
* private undoStack: Stack<{ operation: string; data?: any }>;
*
* constructor() {
* this.content = new SinglyLinkedList<string>();
* this.cursorIndex = 0; // Cursor starts at the beginning
* this.undoStack = new Stack<{ operation: string; data?: any }>(); // Stack to keep track of operations for undo
* }
*
* insert(char: string) {
* this.content.addAt(this.cursorIndex, char);
* this.cursorIndex++;
* this.undoStack.push({ operation: 'insert', data: { index: this.cursorIndex - 1 } });
* }
*
* delete() {
* if (this.cursorIndex === 0) return; // Nothing to delete
* const deleted = this.content.deleteAt(this.cursorIndex - 1);
* this.cursorIndex--;
* this.undoStack.push({ operation: 'delete', data: { index: this.cursorIndex, char: deleted } });
* }
*
* moveCursor(index: number) {
* this.cursorIndex = Math.max(0, Math.min(index, this.content.length));
* }
*
* undo() {
* if (this.undoStack.size === 0) return; // No operations to undo
* const lastAction = this.undoStack.pop();
*
* if (lastAction!.operation === 'insert') {
* this.content.deleteAt(lastAction!.data.index);
* this.cursorIndex = lastAction!.data.index;
* } else if (lastAction!.operation === 'delete') {
* this.content.addAt(lastAction!.data.index, lastAction!.data.char);
* this.cursorIndex = lastAction!.data.index + 1;
* }
* }
*
* getText(): string {
* return [...this.content].join('');
* }
* }
*
* // Example Usage
* const editor = new TextEditor();
* editor.insert('H');
* editor.insert('e');
* editor.insert('l');
* editor.insert('l');
* editor.insert('o');
* console.log(editor.getText()); // 'Hello' // Output: "Hello"
*
* editor.delete();
* console.log(editor.getText()); // 'Hell' // Output: "Hell"
*
* editor.undo();
* console.log(editor.getText()); // 'Hello' // Output: "Hello"
*
* editor.moveCursor(1);
* editor.insert('a');
* console.log(editor.getText()); // 'Haello'
*/
export class SinglyLinkedList<E = any, R = any> extends LinearLinkedBase<E, R, SinglyLinkedListNode<E>> {
constructor(

View file

@ -24,7 +24,7 @@ import { LinearBase } from '../base/linear-base';
* let maxSum = 0;
* let currentSum = 0;
*
* nums.forEach((num, i) => {
* nums.forEach((num) => {
* queue.push(num);
* currentSum += num;
*

View file

@ -15,6 +15,127 @@ import { IterableElementBase } from '../base';
* 4. Function Calls: In most modern programming languages, the records of function calls are managed through a stack. When a function is called, its record (including parameters, local variables, and return address) is 'pushed' into the stack. When the function returns, its record is 'popped' from the stack.
* 5. Expression Evaluation: Used for the evaluation of arithmetic or logical expressions, especially when dealing with parenthesis matching and operator precedence.
* 6. Backtracking Algorithms: In problems where multiple branches need to be explored but only one branch can be explored at a time, stacks can be used to save the state at each branching point.
* @example
* // Balanced Parentheses or Brackets
* type ValidCharacters = ')' | '(' | ']' | '[' | '}' | '{';
*
* const stack = new Stack<string>();
* const input: ValidCharacters[] = '[({})]'.split('') as ValidCharacters[];
* const matches: { [key in ValidCharacters]?: ValidCharacters } = { ')': '(', ']': '[', '}': '{' };
* for (const char of input) {
* if ('([{'.includes(char)) {
* stack.push(char);
* } else if (')]}'.includes(char)) {
* if (stack.pop() !== matches[char]) {
* fail('Parentheses are not balanced');
* }
* }
* }
* console.log(stack.isEmpty()); // true
* @example
* // Expression Evaluation and Conversion
* const stack = new Stack<number>();
* const expression = [5, 3, '+']; // Equivalent to 5 + 3
* expression.forEach(token => {
* if (typeof token === 'number') {
* stack.push(token);
* } else {
* const b = stack.pop()!;
* const a = stack.pop()!;
* stack.push(token === '+' ? a + b : 0); // Only handling '+' here
* }
* });
* console.log(stack.pop()); // 8
* @example
* // Depth-First Search (DFS)
* const stack = new Stack<number>();
* const graph: { [key in number]: number[] } = { 1: [2, 3], 2: [4], 3: [5], 4: [], 5: [] };
* const visited: number[] = [];
* stack.push(1);
* while (!stack.isEmpty()) {
* const node = stack.pop()!;
* if (!visited.includes(node)) {
* visited.push(node);
* graph[node].forEach(neighbor => stack.push(neighbor));
* }
* }
* console.log(visited); // [1, 3, 5, 2, 4]
* @example
* // Backtracking Algorithms
* const stack = new Stack<[number, number]>();
* const maze = [
* ['S', ' ', 'X'],
* ['X', ' ', 'X'],
* [' ', ' ', 'E']
* ];
* const start: [number, number] = [0, 0];
* const end = [2, 2];
* const directions = [
* [0, 1], // To the right
* [1, 0], // down
* [0, -1], // left
* [-1, 0] // up
* ];
*
* const visited = new Set<string>(); // Used to record visited nodes
* stack.push(start);
* const path: number[][] = [];
*
* while (!stack.isEmpty()) {
* const [x, y] = stack.pop()!;
* if (visited.has(`${x},${y}`)) continue; // Skip already visited nodes
* visited.add(`${x},${y}`);
*
* path.push([x, y]);
*
* if (x === end[0] && y === end[1]) {
* break; // Find the end point and exit
* }
*
* for (const [dx, dy] of directions) {
* const nx = x + dx;
* const ny = y + dy;
* if (
* maze[nx]?.[ny] === ' ' || // feasible path
* maze[nx]?.[ny] === 'E' // destination
* ) {
* stack.push([nx, ny]);
* }
* }
* }
*
* expect(path).toContainEqual(end);
* @example
* // Function Call Stack
* const functionStack = new Stack<string>();
* functionStack.push('main');
* functionStack.push('foo');
* functionStack.push('bar');
* console.log(functionStack.pop()); // 'bar'
* console.log(functionStack.pop()); // 'foo'
* console.log(functionStack.pop()); // 'main'
* @example
* // Simplify File Paths
* const stack = new Stack<string>();
* const path = '/a/./b/../../c';
* path.split('/').forEach(segment => {
* if (segment === '..') stack.pop();
* else if (segment && segment !== '.') stack.push(segment);
* });
* console.log(stack.elements.join('/')); // 'c'
* @example
* // Stock Span Problem
* const stack = new Stack<number>();
* const prices = [100, 80, 60, 70, 60, 75, 85];
* const spans: number[] = [];
* prices.forEach((price, i) => {
* while (!stack.isEmpty() && prices[stack.peek()!] <= price) {
* stack.pop();
* }
* spans.push(stack.isEmpty() ? i + 1 : i - stack.peek()!);
* stack.push(i);
* });
* console.log(spans); // [1, 1, 1, 2, 1, 4, 6]
*/
export class Stack<E = any, R = any> extends IterableElementBase<E, R> {
constructor(elements: Iterable<E> | Iterable<R> = [], options?: StackOptions<E, R>) {

View file

@ -341,9 +341,9 @@ describe('Inherit from DirectedGraph and perform operations test2.', () => {
expect(distMap.get(vertex3)).toBe(35);
expect(distMap.get(vertex4)).toBe(14);
expect(distMap.get(vertex5)).toBe(70);
expect(distMap.get(vertex6)).toBe(Infinity);
expect(distMap.get(vertex6)).toBe(Number.MAX_SAFE_INTEGER);
expect(distMap.get(vertex7)).toBe(61);
expect(distMap.get(vertex8)).toBe(Infinity);
expect(distMap.get(vertex8)).toBe(Number.MAX_SAFE_INTEGER);
expect(distMap.get(vertex9)).toBe(19);
expect(preMap).toBeInstanceOf(Map);
@ -351,7 +351,7 @@ describe('Inherit from DirectedGraph and perform operations test2.', () => {
expect(paths).toBeInstanceOf(Array);
expect(paths.length).toBe(0);
expect(min).toBe(Infinity);
expect(min).toBe(Number.MAX_SAFE_INTEGER);
expect(minPath).toBeInstanceOf(Array);
const floydResult = myGraph.floydWarshall();
@ -360,35 +360,35 @@ describe('Inherit from DirectedGraph and perform operations test2.', () => {
const { costs, predecessor } = floydResult;
expect(costs).toBeInstanceOf(Array);
expect(costs.length).toBe(9);
expect(costs[0]).toEqual([32, 12, 35, 14, 70, Infinity, 61, Infinity, 19]);
expect(costs[1]).toEqual([20, 32, 23, 34, 58, Infinity, 81, Infinity, 39]);
expect(costs[2]).toEqual([3, 15, 38, 17, 35, Infinity, 64, Infinity, 22]);
expect(costs[3]).toEqual([123, 135, 120, 137, 155, Infinity, 47, Infinity, 126]);
expect(costs[4]).toEqual([133, 145, 130, 147, 165, Infinity, 57, Infinity, 136]);
expect(costs[0]).toEqual([32, 12, 35, 14, 70, Number.MAX_SAFE_INTEGER, 61, Number.MAX_SAFE_INTEGER, 19]);
expect(costs[1]).toEqual([20, 32, 23, 34, 58, Number.MAX_SAFE_INTEGER, 81, Number.MAX_SAFE_INTEGER, 39]);
expect(costs[2]).toEqual([3, 15, 38, 17, 35, Number.MAX_SAFE_INTEGER, 64, Number.MAX_SAFE_INTEGER, 22]);
expect(costs[3]).toEqual([123, 135, 120, 137, 155, Number.MAX_SAFE_INTEGER, 47, Number.MAX_SAFE_INTEGER, 126]);
expect(costs[4]).toEqual([133, 145, 130, 147, 165, Number.MAX_SAFE_INTEGER, 57, Number.MAX_SAFE_INTEGER, 136]);
expect(costs[5]).toEqual([
Infinity,
Infinity,
Infinity,
Infinity,
Infinity,
Infinity,
Infinity,
Infinity,
Infinity
Number.MAX_SAFE_INTEGER,
Number.MAX_SAFE_INTEGER,
Number.MAX_SAFE_INTEGER,
Number.MAX_SAFE_INTEGER,
Number.MAX_SAFE_INTEGER,
Number.MAX_SAFE_INTEGER,
Number.MAX_SAFE_INTEGER,
Number.MAX_SAFE_INTEGER,
Number.MAX_SAFE_INTEGER
]);
expect(costs[6]).toEqual([76, 88, 73, 90, 108, Infinity, 137, Infinity, 79]);
expect(costs[6]).toEqual([76, 88, 73, 90, 108, Number.MAX_SAFE_INTEGER, 137, Number.MAX_SAFE_INTEGER, 79]);
expect(costs[7]).toEqual([
Infinity,
Infinity,
Infinity,
Infinity,
Infinity,
Infinity,
Infinity,
Infinity,
Infinity
Number.MAX_SAFE_INTEGER,
Number.MAX_SAFE_INTEGER,
Number.MAX_SAFE_INTEGER,
Number.MAX_SAFE_INTEGER,
Number.MAX_SAFE_INTEGER,
Number.MAX_SAFE_INTEGER,
Number.MAX_SAFE_INTEGER,
Number.MAX_SAFE_INTEGER,
Number.MAX_SAFE_INTEGER
]);
expect(costs[8]).toEqual([173, 185, 170, 187, 205, Infinity, 97, Infinity, 176]);
expect(costs[8]).toEqual([173, 185, 170, 187, 205, Number.MAX_SAFE_INTEGER, 97, Number.MAX_SAFE_INTEGER, 176]);
expect(predecessor).toBeInstanceOf(Array);
expect(predecessor.length).toBe(9);
@ -458,12 +458,12 @@ describe('Inherit from DirectedGraph and perform operations test2.', () => {
expect(distMap.size).toBe(9);
expect(distMap.get(vertex1)).toBe(0);
expect(distMap.get(vertex2)).toBe(12);
expect(distMap.get(vertex3)).toBe(Infinity);
expect(distMap.get(vertex3)).toBe(Number.MAX_SAFE_INTEGER);
expect(distMap.get(vertex4)).toBe(14);
expect(distMap.get(vertex5)).toBe(Infinity);
expect(distMap.get(vertex6)).toBe(Infinity);
expect(distMap.get(vertex7)).toBe(Infinity);
expect(distMap.get(vertex8)).toBe(Infinity);
expect(distMap.get(vertex5)).toBe(Number.MAX_SAFE_INTEGER);
expect(distMap.get(vertex6)).toBe(Number.MAX_SAFE_INTEGER);
expect(distMap.get(vertex7)).toBe(Number.MAX_SAFE_INTEGER);
expect(distMap.get(vertex8)).toBe(Number.MAX_SAFE_INTEGER);
expect(distMap.get(vertex9)).toBe(19);
expect(minDist).toBe(12);
@ -512,9 +512,9 @@ describe('Inherit from DirectedGraph and perform operations test2.', () => {
expect(distMap.get(vertex3)).toBe(35);
expect(distMap.get(vertex4)).toBe(14);
expect(distMap.get(vertex5)).toBe(70);
expect(distMap.get(vertex6)).toBe(Infinity);
expect(distMap.get(vertex6)).toBe(Number.MAX_SAFE_INTEGER);
expect(distMap.get(vertex7)).toBe(61);
expect(distMap.get(vertex8)).toBe(Infinity);
expect(distMap.get(vertex8)).toBe(Number.MAX_SAFE_INTEGER);
expect(distMap.get(vertex9)).toBe(19);
expect(minDist).toBe(12);
@ -574,9 +574,9 @@ describe('Inherit from DirectedGraph and perform operations test2.', () => {
expect(distMap.get(vertex3)).toBe(35);
expect(distMap.get(vertex4)).toBe(14);
expect(distMap.get(vertex5)).toBe(70);
expect(distMap.get(vertex6)).toBe(Infinity);
expect(distMap.get(vertex6)).toBe(Number.MAX_SAFE_INTEGER);
expect(distMap.get(vertex7)).toBe(61);
expect(distMap.get(vertex8)).toBe(Infinity);
expect(distMap.get(vertex8)).toBe(Number.MAX_SAFE_INTEGER);
expect(distMap.get(vertex9)).toBe(19);
expect(minDist).toBe(12);

View file

@ -264,7 +264,7 @@ describe('cycles, strongly connected components, bridges, articular points in Un
expect(lowMap.size).toBe(8);
});
it('Should return Infinity if dest is not found', () => {
it('Should return Number.MAX_SAFE_INTEGER if dest is not found', () => {
const graph = new UndirectedGraph<string>();
for (let i = 0; i < 3; ++i) {
@ -274,7 +274,7 @@ it('Should return Infinity if dest is not found', () => {
graph.addEdge(0, 1, 1);
const minCost02 = graph.getMinCostBetween(0, 2, true);
expect(minCost02).toBe(Infinity);
expect(minCost02).toBe(Number.MAX_SAFE_INTEGER);
const minCost01 = graph.getMinCostBetween(0, 1, true);
expect(minCost01).toBe(1);

View file

@ -1,4 +1,4 @@
import { SinglyLinkedList, SinglyLinkedListNode } from '../../../../src';
import { SinglyLinkedList, SinglyLinkedListNode, Stack } from '../../../../src';
describe('SinglyLinkedListNode', () => {
it('should SinglyLinkedList', () => {
@ -649,3 +649,74 @@ describe('iterable methods', () => {
expect(sl.reduce((accumulator, element) => accumulator + element, 0)).toEqual(6);
});
});
describe('classic uses', () => {
it('@example implementation of a basic text editor', () => {
class TextEditor {
private content: SinglyLinkedList<string>;
private cursorIndex: number;
private undoStack: Stack<{ operation: string; data?: any }>;
constructor() {
this.content = new SinglyLinkedList<string>();
this.cursorIndex = 0; // Cursor starts at the beginning
this.undoStack = new Stack<{ operation: string; data?: any }>(); // Stack to keep track of operations for undo
}
insert(char: string) {
this.content.addAt(this.cursorIndex, char);
this.cursorIndex++;
this.undoStack.push({ operation: 'insert', data: { index: this.cursorIndex - 1 } });
}
delete() {
if (this.cursorIndex === 0) return; // Nothing to delete
const deleted = this.content.deleteAt(this.cursorIndex - 1);
this.cursorIndex--;
this.undoStack.push({ operation: 'delete', data: { index: this.cursorIndex, char: deleted } });
}
moveCursor(index: number) {
this.cursorIndex = Math.max(0, Math.min(index, this.content.length));
}
undo() {
if (this.undoStack.size === 0) return; // No operations to undo
const lastAction = this.undoStack.pop();
if (lastAction!.operation === 'insert') {
this.content.deleteAt(lastAction!.data.index);
this.cursorIndex = lastAction!.data.index;
} else if (lastAction!.operation === 'delete') {
this.content.addAt(lastAction!.data.index, lastAction!.data.char);
this.cursorIndex = lastAction!.data.index + 1;
}
}
getText(): string {
return [...this.content].join('');
}
}
// Example Usage
const editor = new TextEditor();
editor.insert('H');
editor.insert('e');
editor.insert('l');
editor.insert('l');
editor.insert('o');
expect(editor.getText()).toBe('Hello'); // Output: "Hello"
editor.delete();
expect(editor.getText()).toBe('Hell'); // Output: "Hell"
editor.undo();
expect(editor.getText()).toBe('Hello'); // Output: "Hello"
editor.moveCursor(1);
editor.insert('a');
expect(editor.getText()).toBe('Haello'); // Output: "Haello"
});
});

View file

@ -603,7 +603,7 @@ describe('classic uses', () => {
let maxSum = 0;
let currentSum = 0;
nums.forEach((num, i) => {
nums.forEach((num) => {
queue.push(num);
currentSum += num;

View file

@ -1,11 +1,11 @@
import { isComparable } from '../../../src';
describe('isNaN', () => {
it('should isNaN', function () {
expect(isNaN('string' as unknown as number)).toBe(true);
});
});
import { isComparable } from '../../../src';
describe('isComparable', () => {
describe('primitive types', () => {
it('numbers should be comparable', () => {
@ -172,3 +172,4 @@ describe('isComparable', () => {
});
});
});