diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..6ff0bf9 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 3a0dbdb..7d8a9a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "data-structure-typed", - "version": "1.15.2", + "version": "1.16.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "data-structure-typed", - "version": "1.15.2", + "version": "1.16.1", "license": "MIT", "devDependencies": { "@types/jest": "^29.5.3", diff --git a/package.json b/package.json index a4f27aa..1c7c87f 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,5 @@ "ts-jest": "^29.1.1", "typedoc": "^0.24.8", "typescript": "^4.9.5" - }, - "dependencies": { } } diff --git a/src/data-structures/linked-list/singly-linked-list.ts b/src/data-structures/linked-list/singly-linked-list.ts index 7527fac..beb80a7 100644 --- a/src/data-structures/linked-list/singly-linked-list.ts +++ b/src/data-structures/linked-list/singly-linked-list.ts @@ -1,757 +1,326 @@ -/** - * data-structure-typed - * - * @author Tyler Zeng - * @copyright Copyright (c) 2022 Tyler Zeng - * @license MIT License - */ +export class SinglyLinkedListNode { - -/* The SinglyLinkedListNode class represents a node in a singly linked list and provides methods for inserting, removing, -and accessing nodes. */ -export class SinglyLinkedListNode { - constructor(val: NodeVal, prev?: SinglyLinkedListNode | null, next?: SinglyLinkedListNode | null, list?: SinglyLinkedList | null) { - this._val = val; - this._prev = prev || null; - this._next = next || null; - this._list = list || null; - } - - protected _val: NodeVal; - - get val(): NodeVal { + private _val: T; + get val(): T { return this._val; } - set val(value: NodeVal) { + set val(value: T) { this._val = value; } - protected _prev: SinglyLinkedListNode | null; - - get prev(): SinglyLinkedListNode | null { - return this._prev; - } - - set prev(value: SinglyLinkedListNode | null) { - this._prev = value; - } - - protected _next: SinglyLinkedListNode | null - - get next(): SinglyLinkedListNode | null { + private _next: SinglyLinkedListNode | null; + get next(): SinglyLinkedListNode | null { return this._next; } - set next(value: SinglyLinkedListNode | null) { + set next(value: SinglyLinkedListNode | null) { this._next = value; } - - protected _list: SinglyLinkedList | null - - get list(): SinglyLinkedList | null { - return this._list; - } - - set list(value: SinglyLinkedList | null) { - this._list = value; - } - - get index() { - if (!this.list) { - return undefined; - } - return this.list.findIndex((value) => value === this.val); - } - - /** - * The `insertBefore` function inserts a new node with the given value before the current node in a singly linked list. - * @param {NodeVal} val - The parameter "val" is of type "NodeVal". It represents the value of the node that you want - * to insert before the current node. - * @returns The method is returning a SinglyLinkedList. - */ - insertBefore(val: NodeVal): SinglyLinkedList { - return this.list !== null - ? this.list.insertBefore(this, val) - : new SinglyLinkedList(val, this.val); - } - - /** - * The function inserts a new node with the given value after the current node in a singly linked list. - * @param {NodeVal} val - The parameter `val` is the value of the node that you want to insert after the current node. - * @returns The method is returning a SinglyLinkedList. - */ - insertAfter(val: NodeVal): SinglyLinkedList { - return this.list !== null - ? this.list.insertAfter(this, val) - : new SinglyLinkedList(this.val, val); - } - - /** - * The `remove()` function removes a node from a singly linked list. - * @returns The remove() method is returning a SinglyLinkedListNode object. - */ - remove(): SinglyLinkedListNode { - if (this.list === null) { - throw new ReferenceError('Node does not belong to any list'); - } - return this.list.removeNode(this); + constructor(val: T) { + this._val = val; + this._next = null; } } -export class SinglyLinkedList { +export class SinglyLinkedList { - /** - * The constructor initializes a linked list with the given arguments as nodes. - * @param {NodeVal[]} args - args is a rest parameter that allows the constructor to accept an arbitrary number of - * arguments of type NodeVal. - */ - constructor(...args: NodeVal[]) { - this._head = null; - this._tail = null; - this._size = 0; - - for (let i = 0; i < arguments.length; i++) { - this.append(args[i]); - } - } - - protected _head: SinglyLinkedListNode | null; - - get head(): SinglyLinkedListNode | null { + private _head: SinglyLinkedListNode | null; + get head(): SinglyLinkedListNode | null { return this._head; } - set head(value: SinglyLinkedListNode | null) { + set head(value: SinglyLinkedListNode | null) { this._head = value; } - protected _tail: SinglyLinkedListNode | null; - - get tail(): SinglyLinkedListNode | null { + private _tail: SinglyLinkedListNode | null; + get tail(): SinglyLinkedListNode | null { return this._tail; } - set tail(value: SinglyLinkedListNode | null) { + set tail(value: SinglyLinkedListNode | null) { this._tail = value; } - protected _size: number; - - get size(): number { - return this._size; + private _length: number; + get length(): number { + return this._length; } - set size(value: number) { - this._size = value; + protected set length(value: number) { + this._length = value; } - /** - * The `from` function in TypeScript creates a new SinglyLinkedList instance from an iterable object. - * @param iterable - The `iterable` parameter is an object that can be iterated over, such as an array or a string. It - * contains a collection of elements of type `T`. - * @returns The method is returning a new instance of the SinglyLinkedList class. - */ - static from(iterable: Iterable): SinglyLinkedList { - return new SinglyLinkedList(...iterable); + constructor() { + this._head = null; + this._tail = null; + this._length = 0; } - /** - * The `get` function returns the value of a node at a given index in a data structure. - * @param {number} index - The index parameter is a number that represents the position of the node in the data - * structure. - * @returns The method is returning the value of the node at the specified index if the node exists, otherwise it - * returns undefined. - */ - get(index: number): NodeVal | undefined { - const node = this.getNode(index); - return node !== undefined ? node.val : undefined; - } - - /** - * The function `getNode` returns the node at a given index in a singly linked list. - * @param {number} index - The `index` parameter is a number that represents the position of the node we want to - * retrieve from the linked list. - * @returns a SinglyLinkedListNode object or undefined. - */ - getNode(index: number): SinglyLinkedListNode | undefined { - if (this.head === null || index < 0 || index >= this.size) { - return undefined; + push(data: T): void { + const newNode = new SinglyLinkedListNode(data); + if (!this.head) { + this.head = newNode; + this.tail = newNode; + } else { + this.tail!.next = newNode; + this.tail = newNode; } - const asc = index < this.size / 2; - const stopAt = asc ? index : this.size - index - 1; - const nextNode = asc ? 'next' : 'prev'; - let currentNode = asc ? this.head : this.tail; - // TODO after no-non-null-assertion not ensure the logic - for (let currentIndex = 0; currentIndex < stopAt; currentIndex++) { - if (currentNode) { - currentNode = currentNode[nextNode]; - } - } - return currentNode || undefined; + this.length++; } - /** - * The function `findNodeIndex` searches for a node in a singly linked list that satisfies a given condition and - * returns its index and the node itself. - * @param callbackFn - The callbackFn parameter is a function that takes three arguments: data, index, and list. It is - * used to determine whether a node in the singly linked list matches a certain condition. The function should return a - * boolean value indicating whether the condition is met for the given node. - * @returns The function `findNodeIndex` returns an object with two properties: `node` and `index`. The `node` property - * contains the node that matches the condition specified in the `callbackFn` function, and the `index` property - * contains the index of that node in the linked list. If no node matches the condition, the function returns - * `undefined`. - */ - findNodeIndex(callbackFn: ( - data: NodeVal, - index: number, - list: SinglyLinkedList, - ) => boolean): ({ - node: SinglyLinkedListNode, - index: number, - }) | undefined { - let currentIndex = 0; - let currentNode = this.head; - while (currentNode) { - if (callbackFn(currentNode.val, currentIndex, this)) { - return { - index: currentIndex, - node: currentNode, - }; + pop(): T | undefined { + if (!this.head) return undefined; + if (this.head === this.tail) { + const val = this.head.val; + this.head = null; + this.tail = null; + this.length--; + return val; + } + + let current = this.head; + while (current.next !== this.tail) { + current = current.next!; + } + const val = this.tail!.val; + current.next = null; + this.tail = current; + this.length--; + return val; + } + + get(index: number): T | undefined { + if (index < 0 || index >= this.length) return undefined; + let current = this.head; + for (let i = 0; i < index; i++) { + current = current!.next; + } + return current!.val; + } + + remove(index: number): T | undefined { + if (index < 0 || index >= this.length) return undefined; + if (index === 0) return this.shift(); + if (index === this.length - 1) return this.pop(); + + let prevNode = this.getNodeAtIndex(index - 1); + const removedNode = prevNode!.next; + prevNode!.next = removedNode!.next; + this.length--; + return removedNode!.val; + } + + private getNodeAtIndex(index: number): SinglyLinkedListNode | null { + let current = this.head; + for (let i = 0; i < index; i++) { + current = current!.next; + } + return current; + } + + shift(): T | undefined { + if (!this.head) return undefined; + const removedNode = this.head; + this.head = this.head.next; + this.length--; + return removedNode.val; + } + + unshift(val: T): void { + const newNode = new SinglyLinkedListNode(val); + if (!this.head) { + this.head = newNode; + this.tail = newNode; + } else { + newNode.next = this.head; + this.head = newNode; + } + this.length++; + } + + insert(index: number, val: T): boolean { + if (index < 0 || index > this.length) return false; + if (index === 0) { + this.unshift(val); + return true; + } + if (index === this.length) { + this.push(val); + return true; + } + + const newNode = new SinglyLinkedListNode(val); + const prevNode = this.getNodeAtIndex(index - 1); + newNode.next = prevNode!.next; + prevNode!.next = newNode; + this.length++; + return true; + } + + + getLength(): number { + return this.length; + } + + isEmpty(): boolean { + return this.length === 0; + } + + clear(): void { + this.head = null; + this.tail = null; + this.length = 0; + } + + toArray(): T[] { + const array: T[] = []; + let current = this.head; + while (current) { + array.push(current.val); + current = current.next; + } + return array; + } + + reverse(): void { + if (!this.head || this.head === this.tail) return; + + let prev: SinglyLinkedListNode | null = null; + let current: SinglyLinkedListNode | null = this.head; + let next: SinglyLinkedListNode | null = null; + + while (current) { + next = current.next; + current.next = prev; + prev = current; + current = next; + } + + [this.head, this.tail] = [this.tail!, this.head!]; + } + + + find(callback: (val: T) => boolean): T | undefined { + let current = this.head; + while (current) { + if (callback(current.val)) { + return current.val; } - currentNode = currentNode.next; - currentIndex += 1; + current = current.next; } return undefined; } - /** - * The findNode function searches for a node in a singly linked list based on a given callback function. - * @param callbackFn - A callback function that takes three parameters: data, index, and list. It returns a boolean - * value indicating whether the current node matches the desired criteria. - * @returns The function `findNode` returns a `SinglyLinkedListNode` if a node satisfying the condition - * specified by the `callbackFn` is found in the linked list. If no such node is found, it returns `undefined`. - */ - findNode(callbackFn: ( - data: NodeVal, - index: number, - list: SinglyLinkedList, - ) => boolean): SinglyLinkedListNode | undefined { - const nodeIndex = this.findNodeIndex(callbackFn); - return nodeIndex !== undefined ? nodeIndex.node : undefined; - } + removeByValue(value: T): boolean { + let current = this.head; + let prev = null; - /** - * The `find` function in TypeScript searches for a node in a singly linked list based on a given callback function and - * returns the value of the found node. - * @param callbackFn - A callback function that takes three parameters: data, index, and list. It returns a boolean - * value indicating whether the condition is met for a particular node in the linked list. - * @returns The method `find` returns the `NodeVal` value of the first node in the linked list that satisfies the - * condition specified by the `callbackFn` function. If no node satisfies the condition, it returns `undefined`. - */ - find(callbackFn: ( - data: NodeVal, - index: number, - list: SinglyLinkedList, - ) => boolean): NodeVal | undefined { - const nodeIndex = this.findNodeIndex(callbackFn); - return nodeIndex !== undefined ? nodeIndex.node.val : undefined; - } - - /** - * The findIndex function returns the index of the first node in a singly linked list that satisfies a given condition, - * or -1 if no such node is found. - * @param callbackFn - A callback function that takes three parameters: data, index, and list. It returns a boolean - * value indicating whether the condition is met for a particular node in the singly linked list. - * @returns The method `findIndex` returns a number. - */ - findIndex(callbackFn: ( - data: NodeVal, - index: number, - list: SinglyLinkedList, - ) => boolean): number { - const nodeIndex = this.findNodeIndex(callbackFn); - return nodeIndex !== undefined ? nodeIndex.index : -1; - } - - /* The above code is a comment in TypeScript. It is using the triple hash symbol ( */ - append(...args: NodeVal[]): SinglyLinkedList { - for (const val of args) { - const node = new SinglyLinkedListNode(val, this.tail, null, this); - if (this.head === null) { - this.head = node; - } - if (this.tail !== null) { - this.tail.next = node; - } - this.tail = node; - this.size += 1; - } - return this; - } - - /** - * The push function appends multiple NodeVal objects to a data structure and returns the new size of the data - * structure. - * @param {NodeVal[]} args - args is a rest parameter of type NodeVal[]. It allows the function to accept any number - * of arguments of type NodeVal. - * @returns The size of the data structure after the nodes are appended. - */ - push(...args: NodeVal[]): number { - this.append(...args); - return this.size; - } - - /** - * The `prepend` function adds new nodes to the beginning of a singly linked list. - * @param {NodeVal[]} args - An array of NodeVal objects. - * @returns The `prepend` method is returning the updated `SinglyLinkedList` object. - */ - prepend(...args: NodeVal[]): SinglyLinkedList { - const reverseArgs = Array.from(args).reverse(); - for (const val of reverseArgs) { - const node = new SinglyLinkedListNode(val, null, this.head, this); - if (this.tail === null) { - this.tail = node; - } - if (this.head !== null) { - this.head.prev = node; - } - this.head = node; - this.size += 1; - } - return this; - } - - /** - * The `insertAt` function inserts a value at a specified index in a singly linked list. - * @param {number} index - The index parameter is a number that represents the position at which the new node should be - * inserted in the linked list. - * @param {NodeVal} val - The `val` parameter represents the value of the node that you want to insert into the linked - * list. - * @returns The method `insertAt` returns the updated `SinglyLinkedList` object. - */ - insertAt(index: number, val: NodeVal): SinglyLinkedList { - if (this.head === null) { - return this.append(val); - } - if (index <= 0) { - return this.prepend(val); - } - - let currentNode = this.head; - let currentIndex = 0; - while (currentIndex < index - 1 && currentNode.next !== null) { - currentIndex += 1; - currentNode = currentNode.next; - } - currentNode.insertAfter(val); - return this; - } - - /** - * The removeNode function removes a node from a singly linked list and updates the head, tail, and size properties - * accordingly. - * @param node - The `node` parameter is of type `SinglyLinkedListNode`, which represents a node in a singly - * linked list. - * @returns the removed node. - */ - removeNode(node: SinglyLinkedListNode): SinglyLinkedListNode { - if (node.list !== this) { - throw new ReferenceError('Node does not belong to this list'); - } - - if (node.prev !== null) { - node.prev.next = node.next; - } - - if (node.next !== null) { - node.next.prev = node.prev; - } - - if (this.head === node) { - this.head = node.next; - } - - if (this.tail === node) { - this.tail = node.prev; - } - - this.size -= 1; - node.next = null; - node.prev = null; - node.list = null; - return node; - } - - /** - * The `removeAt` function removes a node at a specified index from a singly linked list. - * @param {number} index - The index parameter is a number that represents the position of the node to be removed in - * the singly linked list. - * @returns The method `removeAt` returns a `SinglyLinkedListNode` if the node at the specified index is - * found and removed successfully. If the node is not found, it returns `undefined`. - */ - removeAt(index: number): SinglyLinkedListNode | undefined { - const node = this.getNode(index); - return node !== undefined ? this.removeNode(node) : undefined; - } - - /** - * The `insertBefore` function inserts a new node with a given value before a specified reference node in a singly - * linked list. - * @param referenceNode - The referenceNode parameter is the node in the linked list before which the new node will be - * inserted. - * @param {NodeVal} val - The value of the new node that will be inserted before the reference node. - * @returns The method is returning the updated SinglyLinkedList object. - */ - insertBefore( - referenceNode: SinglyLinkedListNode, - val: NodeVal, - ): SinglyLinkedList { - const node = new SinglyLinkedListNode(val, referenceNode.prev, referenceNode, this); - if (referenceNode.prev === null) { - this.head = node; - } - if (referenceNode.prev !== null) { - referenceNode.prev.next = node; - } - referenceNode.prev = node; - this.size += 1; - return this; - } - - /** - * The `sort` function uses the quicksort algorithm to sort the elements of a singly linked list based on a provided - * comparison function. - * @param start - The `start` parameter is the starting node of the sublist that needs to be sorted. - * @param end - The `end` parameter is a reference to the last node in the linked list. It is used as the pivot element - * for the quicksort algorithm. - * @returns The `sort` method is returning the sorted `SinglyLinkedList` object. - */ - sort(compare: (a: NodeVal, b: NodeVal) => boolean): SinglyLinkedList { - if (this.head === null || this.tail === null) { - return this; - } - if (this.size < 2) { - return this; - } - - const quicksort = ( - start: SinglyLinkedListNode, - end: SinglyLinkedListNode, - ) => { - if (start === end) { - return; - } - const pivotData = end.val; - let current: SinglyLinkedListNode | null = start; - let split: SinglyLinkedListNode = start; - while (current && current !== end) { - const sort = compare(current.val, pivotData); - if (sort) { - if (current !== split) { - const temp = split.val; - split.val = current.val; - current.val = temp; + while (current) { + if (current.val === value) { + if (prev === null) { + this.head = current.next; + if (current === this.tail) { + this.tail = null; } - // TODO after no-non-null-assertion not ensure the logic - if (split.next) { - split = split.next; + } else { + prev.next = current.next; + if (current === this.tail) { + this.tail = prev; } } - current = current.next; + this.length--; + return true; } - end.val = split.val; - split.val = pivotData; + prev = current; + current = current.next; + } - if (start.next === end.prev) { - return; + return false; + } + + indexOf(value: T): number { + let index = 0; + let current = this.head; + + while (current) { + if (current.val === value) { + return index; } + index++; + current = current.next; + } - if (split.prev && split !== start) { - quicksort(start, split.prev); + return -1; + } + + findNodeByValue(value: T): SinglyLinkedListNode | null { + let current = this.head; + + while (current) { + if (current.val === value) { + return current; } - if (split.next && split !== end) { - quicksort(split.next, end); + current = current.next; + } + + return null; + } + + insertBefore(existingValue: T, newValue: T): boolean { + if (!this.head) { + return false; + } + + if (this.head.val === existingValue) { + this.unshift(newValue); + return true; + } + + let current = this.head; + while (current.next) { + if (current.next.val === existingValue) { + const newNode = new SinglyLinkedListNode(newValue); + newNode.next = current.next; + current.next = newNode; + this.length++; + return true; } - }; - - quicksort(this.head, this.tail); - return this; - } - - /** - * The `insertAfter` function inserts a new node with a given value after a specified reference node in a singly linked - * list. - * @param referenceNode - The referenceNode parameter is the node after which the new node will be inserted. - * @param {NodeVal} val - The value of the new node that will be inserted after the reference node. - * @returns The `insertAfter` method is returning the updated `SinglyLinkedList` object. - */ - insertAfter( - referenceNode: SinglyLinkedListNode, - val: NodeVal, - ): SinglyLinkedList { - const node = new SinglyLinkedListNode(val, referenceNode, referenceNode.next, this); - if (referenceNode.next === null) { - this.tail = node; - } - if (referenceNode.next !== null) { - referenceNode.next.prev = node; - } - referenceNode.next = node; - this.size += 1; - return this; - } - - /** - * The `shift()` function removes and returns the first element from a linked list. - * @returns The `shift()` method is returning a value of type `NodeVal` or `undefined`. - */ - shift(): NodeVal | undefined { - return this.removeFromAnyEnd(this.head); - } - - /** - * The `pop()` function removes and returns the last element from a linked list. - * @returns The `pop()` method is returning a value of type `NodeVal` or `undefined`. - */ - pop(): NodeVal | undefined { - return this.removeFromAnyEnd(this.tail); - } - - /** - * The merge function merges two singly linked lists by updating the next and prev pointers, as well as the head, tail, - * and size properties. - * @param list - The parameter "list" is a SinglyLinkedList object that contains nodes with data of type NodeVal. - */ - merge(list: SinglyLinkedList): void { - if (this.tail !== null) { - this.tail.next = list.head; - } - if (list.head !== null) { - list.head.prev = this.tail; - } - this.head = this.head || list.head; - this.tail = list.tail || this.tail; - this.size += list.size; - list.size = this.size; - list.head = this.head; - list.tail = this.tail; - } - - /** - * The clear() function resets the linked list by setting the head and tail to null and the size to 0. - * @returns The "this" object is being returned. - */ - clear() { - this.head = null; - this.tail = null; - this.size = 0; - return this; - } - - /** - * The `slice` function returns a new SinglyLinkedList containing a portion of the original list, starting from the - * specified index and ending at the optional end index. - * @param {number} start - The `start` parameter is a number that represents the index at which to start slicing the - * linked list. - * @param {number} [end] - The `end` parameter is an optional number that specifies the index at which to end the - * slicing. If no value is provided for `end`, or if the provided value is less than the `start` index, the slicing - * will continue until the end of the list. - * @returns a new SinglyLinkedList containing the sliced elements from the original list. - */ - slice(start: number, end?: number): SinglyLinkedList { - const list = new SinglyLinkedList(); - let finish = end; - - if (this.head === null || this.tail === null) { - return list; - } - if (finish === undefined || finish < start) { - finish = this.size; + current = current.next; } - let head: SinglyLinkedListNode | null | undefined = this.getNode(start); - for (let i = 0; i < finish - start && head !== null && head !== undefined; i++) { - list.append(head.val); - head = head.next; - } - return list; + return false; } - /** - * The reverse() function reverses the order of nodes in a singly linked list. - * @returns The reverse() method is returning the reversed SinglyLinkedList. - */ - reverse(): SinglyLinkedList { - let currentNode = this.head; - while (currentNode) { - const next = currentNode.next; - currentNode.next = currentNode.prev; - currentNode.prev = next; - currentNode = currentNode.prev; - } - const tail = this.tail; - this.tail = this.head; - this.head = tail; - return this; - } + insertAfter(existingValue: T, newValue: T): boolean { + const existingNode = this.findNodeByValue(existingValue); - /** - * The `forEach` function iterates over a singly linked list and applies a callback function to each node, either in - * forward or reverse order. - * @param callbackFn - A callback function that will be called for each element in the linked list. It takes three - * parameters: - * @param [reverse=false] - A boolean value indicating whether to iterate over the linked list in reverse order. If set - * to true, the iteration will start from the tail of the linked list and move towards the head. If set to false - * (default), the iteration will start from the head and move towards the tail. - */ - forEach(callbackFn: ( - data: any, - index: number, - list: SinglyLinkedList, - ) => any, reverse = false): void { - let currentIndex = reverse ? this.size - 1 : 0; - let currentNode = reverse ? this.tail : this.head; - const modifier = reverse ? -1 : 1; - const nextNode = reverse ? 'prev' : 'next'; - while (currentNode) { - callbackFn(currentNode.val, currentIndex, this); - currentNode = currentNode[nextNode]; - currentIndex += modifier; - } - } - - /** - * The map function takes a callback function and applies it to each element in the linked list, returning a new linked - * list with the results. - * @param callbackFn - A callback function that will be applied to each element in the linked list. It takes three - * parameters: - * @param [reverse=false] - The `reverse` parameter is a boolean value that determines whether the mapping should be - * done in reverse order or not. If `reverse` is set to `true`, the mapping will be done in reverse order. If `reverse` - * is set to `false` or not provided, the mapping will be - * @returns The `map` function is returning a new `SinglyLinkedList` object. - */ - map(callbackFn: ( - data: any, - index: number, - list: SinglyLinkedList, - ) => any, reverse = false): SinglyLinkedList { - const list = new SinglyLinkedList(); - this.forEach((val, index) => list.append(callbackFn(val, index, this)), reverse); - return list; - } - - /** - * The `filter` function filters the elements of a singly linked list based on a given callback function. - * @param callbackFn - A callback function that takes three parameters: data, index, and list. It should return a - * boolean value indicating whether the current element should be included in the filtered list or not. - * @param [reverse=false] - The `reverse` parameter is a boolean value that determines whether the filtered list should - * be reversed or not. If `reverse` is set to `true`, the filtered list will be in reverse order. If `reverse` is set - * to `false` or not provided, the filtered list will be in - * @returns The `filter` method is returning a new `SinglyLinkedList` object. - */ - filter(callbackFn: ( - data: NodeVal, - index: number, - list: SinglyLinkedList, - ) => boolean, reverse = false): SinglyLinkedList { - const list = new SinglyLinkedList(); - this.forEach((val, index) => { - if (callbackFn(val, index, this)) { - list.append(val); + if (existingNode) { + const newNode = new SinglyLinkedListNode(newValue); + newNode.next = existingNode.next; + existingNode.next = newNode; + if (existingNode === this.tail) { + this.tail = newNode; } - }, reverse); - return list; - } - - /** - * The `reduce` function iterates over a singly linked list and applies a callback function to each element, - * accumulating a single value. - * @param callbackFn - A callback function that will be called for each element in the linked list. It takes four - * parameters: - * @param {any} [start] - The `start` parameter is an optional initial value for the accumulator. If provided, the - * `reduce` function will start accumulating from this value. If not provided, the `reduce` function will use the value - * of the first element in the linked list as the initial value. - * @param [reverse=false] - A boolean value indicating whether to iterate over the linked list in reverse order. If set - * to true, the iteration will start from the tail of the linked list and move towards the head. If set to false - * (default), the iteration will start from the head and move towards the tail. - * @returns The `reduce` method returns the accumulated value after applying the callback function to each element in - * the linked list. - */ - reduce( - callbackFn: ( - accumulator: any, - currentNode: NodeVal, - index: number, - list: SinglyLinkedList, - ) => any, - start?: any, - reverse = false, - ): any { - let currentIndex = reverse ? this.size - 1 : 0; - const modifier = reverse ? -1 : 1; - const nextNode = reverse ? 'prev' : 'next'; - let currentElement = reverse ? this.tail : this.head; - let result; - - if (start !== undefined) { - result = start; - } else if (currentElement) { - result = currentElement.val; - currentElement = currentElement[nextNode]; - } else { - throw new TypeError('Reduce of empty LinkedList with no initial value'); + this.length++; + return true; } - while (currentElement) { - result = callbackFn(result, currentElement.val, currentIndex, this); - currentIndex += modifier; - currentElement = currentElement[nextNode]; + return false; + } + + countOccurrences(value: T): number { + let count = 0; + let current = this.head; + + while (current) { + if (current.val === value) { + count++; + } + current = current.next; } - return result; - } - - /** - * The toArray() function converts a NodeVal object into an array of NodeVal objects. - * @returns An array of NodeVal objects. - */ - toArray(): NodeVal[] { - return [...this]; - } - - /** - * The `toString` function takes an optional separator and returns a string representation of an array, with each - * element separated by the specified separator. - * @param [separator= ] - The separator parameter is a string that specifies the character(s) to be used as a separator - * between each element in the array when converting it to a string. By default, the separator is set to a space - * character (' '). - * @returns The toString method is being returned as a string. - */ - toString(separator = ' '): string { - return this.reduce((s, val) => `${s}${separator}${val}`); - } - - /** - * The function is an iterator that returns the values of each node in a linked list. - */ - public* [Symbol.iterator](): IterableIterator { - let element = this.head; - - while (element !== null) { - yield element.val; - element = element.next; - } - } - - /** - * The function removes a node from either end of a singly linked list and returns its value. - * @param {SinglyLinkedListNode | null} node - The `node` parameter is a reference to a node in a singly - * linked list. It can be either a `SinglyLinkedListNode` object or `null`. - * @returns The value of the removed node if the node is not null, otherwise undefined. - */ - protected removeFromAnyEnd(node: SinglyLinkedListNode | null) { - return node !== null ? this.removeNode(node).val : undefined; + return count; } } diff --git a/tests/unit/data-structures/linked-list/doubly-linked-list.ts b/tests/unit/data-structures/linked-list/doubly-linked-list.ts new file mode 100644 index 0000000..8161410 --- /dev/null +++ b/tests/unit/data-structures/linked-list/doubly-linked-list.ts @@ -0,0 +1,12 @@ + +describe('DoublyLinkedList Operation Test', () => { + it('should xxx', function () { + + }); +}); + +describe('DoublyLinkedList Performance Test', () => { + it('should xxx', function () { + + }); +}); \ No newline at end of file diff --git a/tests/unit/data-structures/linked-list/index.ts b/tests/unit/data-structures/linked-list/index.ts new file mode 100644 index 0000000..f4b2fbc --- /dev/null +++ b/tests/unit/data-structures/linked-list/index.ts @@ -0,0 +1,2 @@ +export * from './singly-linked-list.test'; +export * from './doubly-linked-list'; diff --git a/tests/unit/data-structures/linked-list/singly-linked-list.test.ts b/tests/unit/data-structures/linked-list/singly-linked-list.test.ts new file mode 100644 index 0000000..bb8cc0f --- /dev/null +++ b/tests/unit/data-structures/linked-list/singly-linked-list.test.ts @@ -0,0 +1,371 @@ +import {SinglyLinkedList} from '../../../../src'; + +describe('SinglyLinkedList Operation Test', () => { + let list: SinglyLinkedList; + + beforeEach(() => { + list = new SinglyLinkedList(); + }); + + describe('push', () => { + it('should add elements to the end of the list', () => { + list.push(1); + list.push(2); + expect(list.toArray()).toEqual([1, 2]); + }); + }); + + describe('pop', () => { + it('should remove and return the last element of the list', () => { + list.push(1); + list.push(2); + const popped = list.pop(); + expect(popped).toBe(2); + expect(list.toArray()).toEqual([1]); + }); + + it('should return undefined if the list is empty', () => { + const popped = list.pop(); + expect(popped).toBeUndefined(); + }); + }); + + describe('shift', () => { + it('should remove and return the first element of the list', () => { + list.push(1); + list.push(2); + const shifted = list.shift(); + expect(shifted).toBe(1); + expect(list.toArray()).toEqual([2]); + }); + + it('should return undefined if the list is empty', () => { + const shifted = list.shift(); + expect(shifted).toBeUndefined(); + }); + }); + + describe('unshift', () => { + it('should add elements to the beginning of the list', () => { + list.unshift(1); + list.unshift(2); + expect(list.toArray()).toEqual([2, 1]); + }); + }); + + describe('get', () => { + it('should return the element at the specified index', () => { + list.push(1); + list.push(2); + list.push(3); + const element = list.get(1); + expect(element).toBe(2); + }); + + it('should return undefined for an out-of-bounds index', () => { + list.push(1); + const element = list.get(1); + expect(element).toBeUndefined(); + }); + }); + + + describe('insertAfter', () => { + it('should insert an element after an existing value', () => { + list.push(1); + list.push(2); + list.push(3); + list.insertAfter(2, 4); + expect(list.toArray()).toEqual([1, 2, 4, 3]); + }); + + it('should return false if the existing value is not found', () => { + list.push(1); + list.push(2); + list.push(3); + const result = list.insertAfter(5, 4); + expect(result).toBe(false); + expect(list.toArray()).toEqual([1, 2, 3]); + }); + }); + + describe('countOccurrences', () => { + it('should count occurrences of a value in the list', () => { + list.push(1); + list.push(2); + list.push(2); + list.push(3); + const count = list.countOccurrences(2); + expect(count).toBe(2); + }); + + it('should return 0 if the value is not found', () => { + list.push(1); + list.push(2); + const count = list.countOccurrences(3); + expect(count).toBe(0); + }); + }); + + describe('removeValue', () => { + it('should remove the first occurrence of a value from the list', () => { + list.push(1); + list.push(2); + list.push(3); + const removed = list.removeByValue(2); + expect(removed).toBe(true); + expect(list.toArray()).toEqual([1, 3]); + }); + + it('should return false if the value is not found', () => { + list.push(1); + list.push(2); + list.push(3); + const removed = list.removeByValue(4); + expect(removed).toBe(false); + expect(list.toArray()).toEqual([1, 2, 3]); + }); + }); + + + describe('isEmpty', () => { + it('should return true for an empty list', () => { + expect(list.isEmpty()).toBe(true); + }); + + it('should return false for a non-empty list', () => { + list.push(1); + expect(list.isEmpty()).toBe(false); + }); + }); + + describe('clear', () => { + it('should clear all elements from the list', () => { + list.push(1); + list.push(2); + list.push(3); + list.clear(); + expect(list.toArray()).toEqual([]); + expect(list.getLength()).toBe(0); + expect(list.isEmpty()).toBe(true); + }); + }); + + describe('reverse', () => { + it('should reverse the order of elements in the list', () => { + list.push(1); + list.push(2); + list.push(3); + list.reverse(); + expect(list.toArray()).toEqual([3, 2, 1]); + }); + + it('should handle an empty list', () => { + list.reverse(); + expect(list.toArray()).toEqual([]); + }); + + it('should handle a list with a single element', () => { + list.push(1); + list.reverse(); + expect(list.toArray()).toEqual([1]); + }); + }); + + describe('indexOf', () => { + it('should return the index of the first occurrence of a value', () => { + list.push(1); + list.push(2); + list.push(3); + const index = list.indexOf(2); + expect(index).toBe(1); + }); + + it('should return -1 if the value is not found', () => { + list.push(1); + list.push(2); + list.push(3); + const index = list.indexOf(4); + expect(index).toBe(-1); + }); + }); + + describe('toArray', () => { + it('should convert the list to an array', () => { + list.push(1); + list.push(2); + list.push(3); + const array = list.toArray(); + expect(array).toEqual([1, 2, 3]); + }); + + it('should return an empty array for an empty list', () => { + const array = list.toArray(); + expect(array).toEqual([]); + }); + }); + + describe('insertBefore', () => { + it('should insert an element before an existing value', () => { + list.push(1); + list.push(2); + list.push(3); + list.insertBefore(2, 4); + expect(list.toArray()).toEqual([1, 4, 2, 3]); + }); + + it('should insert an element at the beginning', () => { + list.push(1); + list.push(2); + list.insertBefore(1, 3); + expect(list.toArray()).toEqual([3, 1, 2]); + }); + + it('should return false if the existing value is not found', () => { + list.push(1); + list.push(2); + list.push(3); + const result = list.insertBefore(5, 4); + expect(result).toBe(false); + expect(list.toArray()).toEqual([1, 2, 3]); + }); + }); + + describe('getLength', () => { + it('should return the correct length of the list', () => { + expect(list.getLength()).toBe(0); + list.push(1); + list.push(2); + expect(list.getLength()).toBe(2); + }); + }); + + describe('remove', () => { + it('should remove and return the element at the specified index', () => { + list.push(1); + list.push(2); + list.push(3); + const removed = list.remove(1); + expect(removed).toBe(2); + expect(list.toArray()).toEqual([1, 3]); + }); + + it('should return undefined for an out-of-bounds index', () => { + list.push(1); + const removed = list.remove(1); + expect(removed).toBeUndefined(); + }); + + it('should remove and return the first element', () => { + list.push(1); + list.push(2); + const removed = list.remove(0); + expect(removed).toBe(1); + expect(list.toArray()).toEqual([2]); + }); + + it('should remove and return the last element', () => { + list.push(1); + list.push(2); + const removed = list.remove(1); + expect(removed).toBe(2); + expect(list.toArray()).toEqual([1]); + }); + }); + + describe('push and pop', () => { + it('should push and pop elements correctly', () => { + list.push(1); + list.push(2); + expect(list.pop()).toBe(2); + expect(list.pop()).toBe(1); + expect(list.pop()).toBeUndefined(); + }); + }); + + describe('shift and unshift', () => { + it('should shift and unshift elements correctly', () => { + list.unshift(1); + list.unshift(2); + expect(list.shift()).toBe(2); + expect(list.shift()).toBe(1); + expect(list.shift()).toBeUndefined(); + }); + }); + + describe('insert and toArray', () => { + it('should insert elements and return array correctly', () => { + list.insert(0, 1); + list.insert(1, 3); + list.insert(1, 2); + expect(list.toArray()).toEqual([1, 2, 3]); + }); + }); + + describe('find', () => { + it('should find elements using a callback function', () => { + list.push(1); + list.push(2); + list.push(3); + const result = list.find((data) => data % 2 === 0); + expect(result).toBe(2); + }); + + it('should return undefined if element is not found', () => { + list.push(1); + list.push(3); + const result = list.find((data) => data % 2 === 0); + expect(result).toBeUndefined(); + }); + }); + + describe('reverse', () => { + it('should reverse the order of elements', () => { + list.push(1); + list.push(2); + list.push(3); + list.reverse(); + expect(list.toArray()).toEqual([3, 2, 1]); + }); + }); + + describe('countOccurrences', () => { + it('should count occurrences of a value', () => { + list.push(1); + list.push(2); + list.push(2); + list.push(3); + const count = list.countOccurrences(2); + expect(count).toBe(2); + }); + + it('should return 0 if value is not found', () => { + list.push(1); + list.push(2); + const count = list.countOccurrences(3); + expect(count).toBe(0); + }); + }); +}); + +describe('SinglyLinkedList Performance Test', () => { + describe('should the push and pop methods adhere to a time complexity of O(n) and executed correctly under large scale data', () => { + const list = new SinglyLinkedList(); + const iterations = 10000; // Adjust the number of iterations as needed + + const startPushTime = performance.now(); + for (let i = 0; i < iterations; i++) { + list.push(i); + } + expect(performance.now() - startPushTime).toBeLessThan(iterations * 1000); + + const startPopTime = performance.now(); + + for (let i = 0; i < iterations; i++) { + list.pop(); + } + + expect(performance.now() - startPopTime).toBeLessThan(iterations * 1000); + + }); +}); \ No newline at end of file diff --git a/tests/unit/data-structures/linked-list/skip-linked-list.ts b/tests/unit/data-structures/linked-list/skip-linked-list.ts new file mode 100644 index 0000000..3c4f75d --- /dev/null +++ b/tests/unit/data-structures/linked-list/skip-linked-list.ts @@ -0,0 +1,12 @@ + +describe('SkipLinkedList Operation Test', () => { + it('should xxx', function () { + + }); +}); + +describe('SkipLinkedList Performance Test', () => { + it('should xxx', function () { + + }); +}); \ No newline at end of file