From b9dba24bf39996af5c614b27ad3dc7bf41c72b10 Mon Sep 17 00:00:00 2001 From: Revone Date: Sun, 20 Aug 2023 00:12:30 +0800 Subject: [PATCH] re-implemented the SinglyLinkedList and DoublyLinkedList, and completed the unit tests. --- .../linked-list/doubly-linked-list.ts | 576 +++++++++--------- .../linked-list/singly-linked-list.ts | 170 +++--- .../linked-list/doubly-linked-list.test.ts | 367 +++++++++++ .../linked-list/doubly-linked-list.ts | 12 - .../unit/data-structures/linked-list/index.ts | 2 +- .../linked-list/singly-linked-list.test.ts | 58 +- .../linked-list/skip-linked-list.ts | 1 - 7 files changed, 802 insertions(+), 384 deletions(-) create mode 100644 tests/unit/data-structures/linked-list/doubly-linked-list.test.ts delete mode 100644 tests/unit/data-structures/linked-list/doubly-linked-list.ts diff --git a/src/data-structures/linked-list/doubly-linked-list.ts b/src/data-structures/linked-list/doubly-linked-list.ts index 4d0a9c3..0ec3b45 100644 --- a/src/data-structures/linked-list/doubly-linked-list.ts +++ b/src/data-structures/linked-list/doubly-linked-list.ts @@ -5,361 +5,383 @@ * @copyright Copyright (c) 2022 Tyler Zeng * @license MIT License */ -import type {DoublyLinkedListGetBy} from '../types'; - export class DoublyLinkedListNode { - constructor(nodeValue: T) { - this._val = nodeValue; + + constructor(val: T) { + this._val = val; this._next = null; this._prev = null; } - protected _val: T; + private _val: T; get val(): T { return this._val; } - set val(v: T) { - this._val = v; + set val(value: T) { + this._val = value; } - protected _next: DoublyLinkedListNode | null; + private _next: DoublyLinkedListNode | null; get next(): DoublyLinkedListNode | null { return this._next; } - set next(v: DoublyLinkedListNode | null) { - this._next = v; + set next(value: DoublyLinkedListNode | null) { + this._next = value; } - protected _prev: DoublyLinkedListNode | null; + private _prev: DoublyLinkedListNode | null; get prev(): DoublyLinkedListNode | null { return this._prev; } - set prev(v: DoublyLinkedListNode | null) { - this._prev = v; + set prev(value: DoublyLinkedListNode | null) { + this._prev = value; } } export class DoublyLinkedList { + constructor() { - this._first = null; - this._last = null; - this._size = 0; + this._head = null; + this._tail = null; + this._length = 0; } - protected _first: DoublyLinkedListNode | null; + private _head: DoublyLinkedListNode | null; - get first(): DoublyLinkedListNode | null { - return this._first; + get head(): DoublyLinkedListNode | null { + return this._head; } - protected set first(v: DoublyLinkedListNode | null) { - this._first = v; + set head(value: DoublyLinkedListNode | null) { + this._head = value; } - protected _last: DoublyLinkedListNode | null; - get last(): DoublyLinkedListNode | null { - return this._last; + private _tail: DoublyLinkedListNode | null; + + get tail(): DoublyLinkedListNode | null { + return this._tail; } - protected set last(v: DoublyLinkedListNode | null) { - this._last = v; + set tail(value: DoublyLinkedListNode | null) { + this._tail = value; } - protected _size: number; + private _length: number; - get size(): number { - return this._size; + get length(): number { + return this._length; } - protected set size(v: number) { - this._size = v; + protected set length(value: number) { + this._length = value; } - /** - * Starting from TypeScript version 5.0 and onwards, the use of distinct access modifiers for Getters and Setters is not permitted. As an alternative, to ensure compatibility, it is necessary to adopt a Java-style approach for Setters (using the same name as the property) while utilizing separate method names for Getters. - */ - getFirst(): DoublyLinkedListNode | null { - return this._first; + getLength(): number { + return this._length; } - /** - * Starting from TypeScript version 5.0 and onwards, the use of distinct access modifiers for Getters and Setters is not permitted. As an alternative, to ensure compatibility, it is necessary to adopt a Java-style approach for Setters (using the same name as the property) while utilizing separate method names for Getters. - */ - getLast(): DoublyLinkedListNode | null { - return this._last; - } - - /** - * Starting from TypeScript version 5.0 and onwards, the use of distinct access modifiers for Getters and Setters is not permitted. As an alternative, to ensure compatibility, it is necessary to adopt a Java-style approach for Setters (using the same name as the property) while utilizing separate method names for Getters. - */ - getSize(): number { - return this._size; - } - - /** - * The function adds a new node with a given value to the beginning of a doubly linked list. - * @param {T} val - The `val` parameter represents the value of the element that you want to add to the beginning of - * the doubly linked list. - * @returns A boolean value is being returned. - */ - addFirst(val: T): boolean { + push(val: T): void { const newNode = new DoublyLinkedListNode(val); - if (this._size === 0) { - this._first = newNode; - this._last = newNode; + if (!this.head) { + this.head = newNode; + this.tail = newNode; } else { - if (this._first) this._first.prev = newNode; - newNode.next = this._first; - this._first = newNode; + newNode.prev = this.tail; + this.tail!.next = newNode; + this.tail = newNode; } - this._size++; - return true; + this.length++; } - /** - * The function adds a new node with a given value to the end of a doubly linked list. - * @param {T} val - The `val` parameter represents the value of the element that you want to add to the end of the - * doubly linked list. - * @returns a boolean value, which is always true. - */ - addLast(val: T): boolean { + pop(): T | null { + if (!this.tail) return null; + const removedNode = this.tail; + if (this.head === this.tail) { + this.head = null; + this.tail = null; + } else { + this.tail = removedNode.prev; + this.tail!.next = null; + } + this.length--; + return removedNode.val; + } + + shift(): T | null { + if (!this.head) return null; + const removedNode = this.head; + if (this.head === this.tail) { + this.head = null; + this.tail = null; + } else { + this.head = removedNode.next; + this.head!.prev = null; + } + this.length--; + return removedNode.val; + } + + unshift(val: T): void { const newNode = new DoublyLinkedListNode(val); - if (this._size === 0) { - this._first = newNode; - this._last = newNode; + if (!this.head) { + this.head = newNode; + this.tail = newNode; } else { - if (this._last) this._last.next = newNode; - newNode.prev = this._last; - this._last = newNode; + newNode.next = this.head; + this.head!.prev = newNode; + this.head = newNode; } - this._size++; - return true; + this.length++; } - peekFirst(): T | null; - peekFirst(by: 'val'): T | null; - peekFirst(by: 'node'): DoublyLinkedListNode | null; - /** - * The `peekFirst` function returns the first node or value in a doubly linked list, depending on the specified - * parameter. - * @param {DoublyLinkedListGetBy} [by] - The "by" parameter is an optional parameter of type DoublyLinkedListGetBy. It - * is used to specify whether to return the first node, the value of the first node, or the first node itself. - * @returns The method `peekFirst` returns either the first node of the doubly linked list (`DoublyLinkedListNode`), - * the value of the first node (`T`), or `null` depending on the value of the `by` parameter. - */ - peekFirst(by?: DoublyLinkedListGetBy): T | DoublyLinkedListNode | null { - switch (by) { - case 'node': - return this._first ?? null; - case 'val': - return this._first?.val ?? null; - default: - return this._first?.val ?? null; + get(index: number): T | null { + if (index < 0 || index >= this.length) return null; + let current = this.head; + for (let i = 0; i < index; i++) { + current = current!.next; } + return current!.val; } - peekLast(): T | null; - peekLast(by: 'val'): T | null; - peekLast(by: 'node'): DoublyLinkedListNode | null; - /** - * The `peekLast` function returns the last node or value in a doubly linked list. - * @param {DoublyLinkedListGetBy} [by=val] - The "by" parameter is an optional parameter of type DoublyLinkedListGetBy. - * It specifies whether to return the last node, the value of the last node, or both. The default value is 'val', which - * means that if no value is provided for the "by" parameter, the method - * @returns The method `peekLast` returns the last node, value, or null based on the specified `by` parameter. - */ - peekLast(by: DoublyLinkedListGetBy = 'val'): T | DoublyLinkedListNode | null { - switch (by) { - case 'node': - return this._last ?? null; - case 'val': - return this._last?.val ?? null; - default: - return this._last?.val ?? null; + getNodeAt(index: number): DoublyLinkedListNode | null { + if (index < 0 || index >= this.length) return null; + let current = this.head; + for (let i = 0; i < index; i++) { + current = current!.next; } + return current; } - pollFirst(): T | null; - pollFirst(by: 'val'): T | null; - pollFirst(by: 'node'): DoublyLinkedListNode | null; - /** - * The function `pollFirst` removes and returns the first element of a doubly linked list, either as a node or its - * value, depending on the specified parameter. - * @param {DoublyLinkedListGetBy} [by=val] - The "by" parameter is an optional parameter of type DoublyLinkedListGetBy. - * It specifies the criteria by which the first element should be retrieved from the doubly linked list. The default - * value is 'val', which means the first element will be retrieved by its value. Other possible values for "by - * @returns The method `pollFirst` returns either the value of the first node in the doubly linked list, the first node - * itself, or null if the list is empty. The specific return type depends on the value of the `by` parameter. If `by` - * is set to 'node', the method returns the first node. If `by` is set to 'val', the method returns the value - */ - pollFirst(by: DoublyLinkedListGetBy = 'val'): T | DoublyLinkedListNode | null { - if (this._size === 0) return null; - const oldHead = this._first; - if (this._size === 1) { - this._first = null; - this._last = null; - } else { - this._first = oldHead?.next ?? null; - if (this._first) this._first.prev = null; - if (oldHead) oldHead.next = null; - } - this._size--; - switch (by) { - case 'node': - return oldHead ?? null; - case 'val': - return oldHead?.val ?? null; - default: - return oldHead?.val ?? null; - } - } + findNodeByValue(val: T): DoublyLinkedListNode | null { + let current = this.head; - pollLast(): T | null; - pollLast(by: 'val'): T | null; - pollLast(by: 'node'): DoublyLinkedListNode | null; - /** - * The function `pollLast` removes and returns the last element in a doubly linked list, either as a node or its value, - * depending on the specified parameter. - * @param {DoublyLinkedListGetBy} [by=val] - The parameter "by" is of type DoublyLinkedListGetBy, which is an enum that - * can have two possible values: 'node' or 'val'. It determines the type of value that will be returned by the pollLast - * method. If 'node' is specified, the method will return the - * @returns The method `pollLast` returns either a `DoublyLinkedListNode`, the value of the node (`T`), or `null`. - * The specific type that is returned depends on the value of the `by` parameter. If `by` is set to `'node'`, then a - * `DoublyLinkedListNode` is returned. If `by` is set to `' - */ - pollLast(by: DoublyLinkedListGetBy = 'val'): DoublyLinkedListNode | T | null { - if (this._size === 0) return null; - const polled = this._last; - if (this._size === 1) { - this._first = null; - this._last = null; - } else { - this._last = polled?.prev ?? null; - if (this._last) this._last.next = null; - if (polled) polled.prev = null; - } - this._size--; - switch (by) { - case 'node': - return polled ?? null; - case 'val': - return polled?.val ?? null; - default: - return polled?.val ?? null; - } - } - - get(index: number): T | null; - get(index: number, by: 'node'): DoublyLinkedListNode | null; - get(index: number, by: 'val'): T | null; - /** - * Returns the node at the specified index of the linked list. - * If index = 0; first element in the list is returned. - * If index = 3; fourth element in the list is returned. - * @param index Index of the node to be retrieved - * @param by Return value type - */ - get(index: number, by: DoublyLinkedListGetBy = 'val'): T | DoublyLinkedListNode | null { - if (index < 0 || index >= this._size) return null; - let count, current; - if (index <= this._size / 2) { - count = 0; - current = this._first; - while (count !== index) { - current = current?.next; - count++; - } - } else { - count = this._size - 1; - current = this._last; - while (count !== index) { - current = current?.prev; - count--; + while (current) { + if (current.val === val) { + return current; } + current = current.next; } - switch (by) { - case 'node': - return current ?? null; - case 'val': - return current?.val ?? null; - default: - return current?.val ?? null; - } + + return null; } - /** - * Updates the value of the node at the specified index. - * If index = 0; Value of the first element in the list is updated. - * If index = 3; Value of the fourth element in the list is updated. - * @param index Index of the node to be updated - * @param val New value of the node - */ - set(index: number, val: T): boolean { - const foundNode = this.get(index, 'node'); - if (foundNode !== null) { - foundNode.val = val; + 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 DoublyLinkedListNode(val); + const prevNode = this.getNodeAt(index - 1); + const nextNode = prevNode!.next; + newNode.prev = prevNode; + newNode.next = nextNode; + prevNode!.next = newNode; + nextNode!.prev = newNode; + this.length++; + return true; + } + + deleteAt(index: number): T | null { + if (index < 0 || index >= this.length) return null; + if (index === 0) return this.shift(); + if (index === this.length - 1) return this.pop(); + + const removedNode = this.getNodeAt(index); + const prevNode = removedNode!.prev; + const nextNode = removedNode!.next; + prevNode!.next = nextNode; + nextNode!.prev = prevNode; + this.length--; + return removedNode!.val; + } + + delete(val: T): boolean { + let current = this.head; + while (current) { + if (current.val === val) { + if (current === this.head) { + this.shift(); + } else if (current === this.tail) { + this.pop(); + } else { + const prevNode = current.prev; + const nextNode = current.next; + prevNode!.next = nextNode; + nextNode!.prev = prevNode; + this.length--; + } + return true; + } + current = current.next; + } return false; } - isEmpty() { - return this._size === 0; + toArray(): T[] { + const array: T[] = []; + let current = this.head; + while (current) { + array.push(current.val); + current = current.next; + } + return array; } - // --- start extra methods --- - /** - * Inserts a new node at the specified index. - * @param index Index at which the new node has to be inserted - * @param val Value of the new node to be inserted - */ - insert(index: number, val: T): boolean { - if (index < 0 || index > this._size) return false; - if (index === 0) return !!this.addFirst(val); - if (index === this._size) return !!this.addLast(val); - - const newNode = new DoublyLinkedListNode(val); - const prevNode = this.get(index - 1, 'node'); - const nextNode = prevNode?.next; - - if (prevNode) prevNode.next = newNode; - newNode.prev = prevNode; - newNode.next = nextNode ?? null; - if (nextNode) nextNode.prev = newNode; - this._size++; - return true; + clear(): void { + this._head = null; + this._tail = null; + this._length = 0; } - /** - * The `remove` function removes an element at a specified index from a data structure, updating the links between - * nodes accordingly. - * @param {number} index - The index parameter represents the position of the element to be removed in the data - * structure. It is of type number. - * @returns The `remove` method returns the value of the removed element (`T`) if the removal is successful, or `null` - * if the index is out of bounds. - */ - remove(index: number): T | null { - if (index < 0 || index > this._size - 1) return null; - else if (index === 0) return this.pollFirst(); - else if (index === this._size - 1) return this.pollLast('node')?.val ?? null; - else { - const prevNode = this.get(index - 1, 'node'); - const removeNode = prevNode?.next; - const nextNode = removeNode?.next; - if (prevNode) prevNode.next = nextNode ?? null; - if (nextNode) nextNode.prev = prevNode; - if (removeNode) removeNode.next = null; - if (removeNode) removeNode.prev = null; - this._size--; - return removeNode?.val ?? null; + find(callback: (val: T) => boolean): T | null { + let current = this.head; + while (current) { + if (callback(current.val)) { + return current.val; + } + current = current.next; + } + return null; + } + + indexOf(val: T): number { + let index = 0; + let current = this.head; + while (current) { + if (current.val === val) { + return index; + } + index++; + current = current.next; + } + return -1; + } + + findLast(callback: (val: T) => boolean): T | null { + let current = this.tail; + while (current) { + if (callback(current.val)) { + return current.val; + } + current = current.prev; + } + return null; + } + + toArrayReverse(): T[] { + const array: T[] = []; + let current = this.tail; + while (current) { + array.push(current.val); + current = current.prev; + } + return array; + } + + reverse(): void { + let current = this.head; + [this.head, this.tail] = [this.tail, this.head]; + while (current) { + const next = current.next; + [current.prev, current.next] = [current.next, current.prev]; + current = next; } } - // --- end extra methods --- + forEach(callback: (val: T, index: number) => void): void { + let current = this.head; + let index = 0; + while (current) { + callback(current.val, index); + current = current.next; + index++; + } + } + + map(callback: (val: T) => U): DoublyLinkedList { + const mappedList = new DoublyLinkedList(); + let current = this.head; + while (current) { + mappedList.push(callback(current.val)); + current = current.next; + } + return mappedList; + } + + filter(callback: (val: T) => boolean): DoublyLinkedList { + const filteredList = new DoublyLinkedList(); + let current = this.head; + while (current) { + if (callback(current.val)) { + filteredList.push(current.val); + } + current = current.next; + } + return filteredList; + } + + reduce(callback: (accumulator: U, val: T) => U, initialValue: U): U { + let accumulator = initialValue; + let current = this.head; + while (current) { + accumulator = callback(accumulator, current.val); + current = current.next; + } + return accumulator; + } + + insertAfter(existingValue: T, newValue: T): boolean { + const existingNode = this.findNodeByValue(existingValue); + + if (existingNode) { + const newNode = new DoublyLinkedListNode(newValue); + newNode.next = existingNode.next; + if (existingNode.next) { + existingNode.next.prev = newNode; + } + newNode.prev = existingNode; + existingNode.next = newNode; + if (existingNode === this.tail) { + this.tail = newNode; + } + this.length++; + return true; + } + + return false; + } + + insertBefore(existingValue: T, newValue: T): boolean { + const existingNode = this.findNodeByValue(existingValue); + + if (existingNode) { + const newNode = new DoublyLinkedListNode(newValue); + newNode.prev = existingNode.prev; + if (existingNode.prev) { + existingNode.prev.next = newNode; + } + newNode.next = existingNode; + existingNode.prev = newNode; + if (existingNode === this.head) { + this.head = newNode; + } + this.length++; + return true; + } + + return false; + } } + diff --git a/src/data-structures/linked-list/singly-linked-list.ts b/src/data-structures/linked-list/singly-linked-list.ts index beb80a7..2d5760c 100644 --- a/src/data-structures/linked-list/singly-linked-list.ts +++ b/src/data-structures/linked-list/singly-linked-list.ts @@ -1,6 +1,19 @@ +/** + * data-structure-typed + * + * @author Tyler Zeng + * @copyright Copyright (c) 2022 Tyler Zeng + * @license MIT License + */ export class SinglyLinkedListNode { + constructor(val: T) { + this._val = val; + this._next = null; + } + private _val: T; + get val(): T { return this._val; } @@ -10,6 +23,7 @@ export class SinglyLinkedListNode { } private _next: SinglyLinkedListNode | null; + get next(): SinglyLinkedListNode | null { return this._next; } @@ -17,15 +31,18 @@ export class SinglyLinkedListNode { set next(value: SinglyLinkedListNode | null) { this._next = value; } - constructor(val: T) { - this._val = val; - this._next = null; - } } export class SinglyLinkedList { + constructor() { + this._head = null; + this._tail = null; + this._length = 0; + } + private _head: SinglyLinkedListNode | null; + get head(): SinglyLinkedListNode | null { return this._head; } @@ -35,6 +52,7 @@ export class SinglyLinkedList { } private _tail: SinglyLinkedListNode | null; + get tail(): SinglyLinkedListNode | null { return this._tail; } @@ -44,6 +62,7 @@ export class SinglyLinkedList { } private _length: number; + get length(): number { return this._length; } @@ -52,10 +71,8 @@ export class SinglyLinkedList { this._length = value; } - constructor() { - this._head = null; - this._tail = null; - this._length = 0; + getLength(): number { + return this._length; } push(data: T): void { @@ -70,8 +87,8 @@ export class SinglyLinkedList { this.length++; } - pop(): T | undefined { - if (!this.head) return undefined; + pop(): T | null { + if (!this.head) return null; if (this.head === this.tail) { const val = this.head.val; this.head = null; @@ -91,37 +108,8 @@ export class SinglyLinkedList { 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; + shift(): T | null { + if (!this.head) return null; const removedNode = this.head; this.head = this.head.next; this.length--; @@ -140,6 +128,62 @@ export class SinglyLinkedList { this.length++; } + get(index: number): T | null { + if (index < 0 || index >= this.length) return null; + let current = this.head; + for (let i = 0; i < index; i++) { + current = current!.next; + } + return current!.val; + } + + deleteAt(index: number): T | null { + if (index < 0 || index >= this.length) return null; + if (index === 0) return this.shift(); + if (index === this.length - 1) return this.pop(); + + let prevNode = this.getNodeAt(index - 1); + const removedNode = prevNode!.next; + prevNode!.next = removedNode!.next; + this.length--; + return removedNode!.val; + } + + delete(value: T): boolean { + let current = this.head; + let prev = null; + + while (current) { + if (current.val === value) { + if (prev === null) { + this.head = current.next; + if (current === this.tail) { + this.tail = null; + } + } else { + prev.next = current.next; + if (current === this.tail) { + this.tail = prev; + } + } + this.length--; + return true; + } + prev = current; + current = current.next; + } + + return false; + } + + getNodeAt(index: number): SinglyLinkedListNode | null { + let current = this.head; + for (let i = 0; i < index; i++) { + current = current!.next; + } + return current; + } + insert(index: number, val: T): boolean { if (index < 0 || index > this.length) return false; if (index === 0) { @@ -152,26 +196,21 @@ export class SinglyLinkedList { } const newNode = new SinglyLinkedListNode(val); - const prevNode = this.getNodeAtIndex(index - 1); + const prevNode = this.getNodeAt(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; + this._head = null; + this._tail = null; + this._length = 0; } toArray(): T[] { @@ -201,8 +240,7 @@ export class SinglyLinkedList { [this.head, this.tail] = [this.tail!, this.head!]; } - - find(callback: (val: T) => boolean): T | undefined { + find(callback: (val: T) => boolean): T | null { let current = this.head; while (current) { if (callback(current.val)) { @@ -210,35 +248,9 @@ export class SinglyLinkedList { } current = current.next; } - return undefined; + return null; } - removeByValue(value: T): boolean { - let current = this.head; - let prev = null; - - while (current) { - if (current.val === value) { - if (prev === null) { - this.head = current.next; - if (current === this.tail) { - this.tail = null; - } - } else { - prev.next = current.next; - if (current === this.tail) { - this.tail = prev; - } - } - this.length--; - return true; - } - prev = current; - current = current.next; - } - - return false; - } indexOf(value: T): number { let index = 0; diff --git a/tests/unit/data-structures/linked-list/doubly-linked-list.test.ts b/tests/unit/data-structures/linked-list/doubly-linked-list.test.ts new file mode 100644 index 0000000..ebdf20a --- /dev/null +++ b/tests/unit/data-structures/linked-list/doubly-linked-list.test.ts @@ -0,0 +1,367 @@ +import {DoublyLinkedList} from '../../../../src'; + +describe('DoublyLinkedList Operation Test', () => { + let list: DoublyLinkedList; + let objectList: DoublyLinkedList<{ keyA: number }>; + + beforeEach(() => { + list = new DoublyLinkedList(); + objectList = new DoublyLinkedList(); + }); + + it('should initialize an empty list', () => { + expect(list.length).toBe(0); + expect(list.head).toBeNull(); + expect(list.tail).toBeNull(); + }); + + it('should push elements to the list', () => { + list.push(1); + list.push(2); + list.push(3); + expect(list.length).toBe(3); + expect(list.head!.val).toBe(1); + expect(list.tail!.val).toBe(3); + }); + + it('should pop elements from the end of the list', () => { + list.push(1); + list.push(2); + const poppedValue = list.pop(); + expect(poppedValue).toBe(2); + expect(list.length).toBe(1); + expect(list.head!.val).toBe(1); + expect(list.tail!.val).toBe(1); + }); + it('should insert elements at specific positions', () => { + list.push(1); + list.push(2); + list.push(3); + + // Inserting at the beginning + list.insert(0, 0); + expect(list.length).toBe(4); + expect(list.get(0)).toBe(0); + expect(list.get(1)).toBe(1); + + // Inserting in the middle + list.insert(2, 1.5); + expect(list.length).toBe(5); + expect(list.get(2)).toBe(1.5); + expect(list.get(3)).toBe(2); + + // Inserting at the end + list.insert(5, 4); + expect(list.length).toBe(6); + expect(list.get(5)).toBe(4); + expect(list.tail!.val).toBe(4); + }); + + it('should delete elements at specific positions', () => { + list.push(1); + list.push(2); + list.push(3); + + // Deleting from the beginning + const deletedValue = list.deleteAt(0); + expect(deletedValue).toBe(1); + expect(list.length).toBe(2); + expect(list.head!.val).toBe(2); + + // Deleting from the middle + list.deleteAt(0); // Deleting the second element + expect(list.length).toBe(1); + expect(list.head!.val).toBe(3); + + // Deleting from the end + list.deleteAt(0); + expect(list.length).toBe(0); + expect(list.head).toBeNull(); + expect(list.tail).toBeNull(); + }); + + it('should delete elements by value', () => { + list.push(1); + list.push(2); + list.push(3); + + list.delete(2); + expect(list.length).toBe(2); + expect(list.head!.val).toBe(1); + expect(list.tail!.val).toBe(3); + + list.delete(1); + expect(list.length).toBe(1); + expect(list.head!.val).toBe(3); + + list.delete(3); + expect(list.length).toBe(0); + expect(list.head).toBeNull(); + expect(list.tail).toBeNull(); + }); + + it('should reverse the linked list', () => { + list.push(1); + list.push(2); + list.push(3); + + list.reverse(); + + expect(list.toArray()).toEqual([3, 2, 1]); + expect(list.toArrayReverse()).toEqual([1, 2, 3]); + }); + + it('should map elements using a callback function', () => { + list.push(1); + list.push(2); + list.push(3); + + const mappedList = list.map((val) => val * 2); + + expect(mappedList.toArray()).toEqual([2, 4, 6]); + }); + + it('should filter elements using a callback function', () => { + list.push(1); + list.push(2); + list.push(3); + list.push(4); + + const filteredList = list.filter((val) => val % 2 === 0); + + expect(filteredList.toArray()).toEqual([2, 4]); + }); + + it('should reduce elements using a callback function and an initial value', () => { + list.push(1); + list.push(2); + list.push(3); + list.push(4); + + const sum = list.reduce((acc, val) => acc + val, 0); + + expect(sum).toBe(10); + }); + + it('should insert an element after a specific value', () => { + list.push(1); + list.push(2); + list.push(3); + + list.insertAfter(2, 2.5); + + expect(list.toArray()).toEqual([1, 2, 2.5, 3]); + }); + + it('should insert an element before a specific value', () => { + list.push(1); + list.push(2); + list.push(3); + + list.insertBefore(2, 1.5); + + expect(list.toArray()).toEqual([1, 1.5, 2, 3]); + }); + it('should find the first element that satisfies a condition', () => { + list.push(1); + list.push(2); + list.push(3); + + const found = list.find((val) => val % 2 === 0); + + expect(found).toBe(2); + }); + + it('should find the index of an element', () => { + list.push(1); + list.push(2); + list.push(3); + + const index = list.indexOf(2); + + expect(index).toBe(1); + }); + + it('should find the last element that satisfies a condition', () => { + list.push(1); + list.push(2); + list.push(3); + list.push(4); + + const lastEven = list.findLast((val) => val % 2 === 0); + + expect(lastEven).toBe(4); + }); + + it('should clear the linked list', () => { + list.push(1); + list.push(2); + list.push(3); + + list.clear(); + + expect(list.getLength()).toBe(0); + expect(list.head).toBe(null); + expect(list.tail).toBe(null); + }); + + it('should create a reversed array of values', () => { + list.push(1); + list.push(2); + list.push(3); + + const reversedArray = list.toArrayReverse(); + + expect(reversedArray).toEqual([3, 2, 1]); + }); + + it('should reverse the linked list', () => { + list.push(1); + list.push(2); + list.push(3); + + list.reverse(); + + expect(list.toArray()).toEqual([3, 2, 1]); + expect(list.head?.val).toBe(3); + expect(list.tail?.val).toBe(1); + }); + + it('should iterate over each element and apply a callback', () => { + list.push(1); + list.push(2); + list.push(3); + + const result: number[] = []; + list.forEach((val) => { + result.push(val * 2); + }); + + expect(result).toEqual([2, 4, 6]); + }); + + it('should create a new linked list by applying a mapping function', () => { + list.push(1); + list.push(2); + list.push(3); + + const mappedList = list.map((val) => val * 2); + + expect(mappedList.toArray()).toEqual([2, 4, 6]); + }); + + it('should create a new linked list by filtering elements', () => { + list.push(1); + list.push(2); + list.push(3); + list.push(4); + + const filteredList = list.filter((val) => val % 2 === 0); + + expect(filteredList.toArray()).toEqual([2, 4]); + }); + + it('should reduce the linked list to a single value', () => { + list.push(1); + list.push(2); + list.push(3); + + const sum = list.reduce((acc, val) => acc + val, 0); + + expect(sum).toBe(6); + }); + + it('should insert a new value after an existing value', () => { + list.push(1); + list.push(2); + list.push(3); + + const success = list.insertAfter(2, 4); + expect(success).toBe(true); + expect(list.toArray()).toEqual([1, 2, 4, 3]); + }); + + it('should insert a new value before an existing value', () => { + list.push(1); + list.push(2); + list.push(3); + + const success = list.insertBefore(2, 0); + expect(success).toBe(true); + expect(list.toArray()).toEqual([1, 0, 2, 3]); + }); + + it('should not insert a new value after a non-existing value', () => { + list.push(1); + list.push(2); + list.push(3); + + const success = list.insertAfter(4, 5); + expect(success).toBe(false); + expect(list.toArray()).toEqual([1, 2, 3]); + }); + + it('should not insert a new value before a non-existing value', () => { + list.push(1); + list.push(2); + list.push(3); + + const success = list.insertBefore(4, 0); + expect(success).toBe(false); + expect(list.toArray()).toEqual([1, 2, 3]); + }); + + it('should insert and manipulate objects with numeric properties', () => { + const obj1 = {keyA: 10}; + const obj2 = {keyA: 20}; + const obj3 = {keyA: 30}; + + objectList.push(obj1); + objectList.push(obj2); + objectList.push(obj3); + + expect(objectList.toArray()).toEqual([obj1, obj2, obj3]); + + const newObj = {keyA: 25}; // Corrected newObj value + const insertSuccess = objectList.insertBefore(obj2, newObj); + expect(insertSuccess).toBe(true); + + const findNode = objectList.findNodeByValue(newObj); // Use newObj instead of obj2 + expect(findNode?.val).toEqual(newObj); + + const deleted = objectList.delete(newObj); // Use newObj instead of obj2 + expect(deleted).toBe(true); + + const poppedObj = objectList.pop(); + expect(poppedObj).toBe(obj3); + + const shiftedObj = objectList.shift(); + expect(shiftedObj).toBe(obj1); + }); + +}); + +describe('DoublyLinkedList 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 DoublyLinkedList(); + const iterations = 10000; + + const startPushTime = performance.now(); + for (let i = 0; i < iterations; i++) { + list.unshift(i); + } + expect(performance.now() - startPushTime).toBeLessThan(iterations * 1000); + + const startPopTime = performance.now(); + + expect(list.length).toBeGreaterThan(0); + for (let i = 0; i < iterations; i++) { + list.shift(); + } + expect(list.pop()).toBeNull(); + expect(list.length).toBe(0); + expect(performance.now() - startPopTime).toBeLessThan(iterations * 1000); + + }); + +}); \ No newline at end of file diff --git a/tests/unit/data-structures/linked-list/doubly-linked-list.ts b/tests/unit/data-structures/linked-list/doubly-linked-list.ts deleted file mode 100644 index 8161410..0000000 --- a/tests/unit/data-structures/linked-list/doubly-linked-list.ts +++ /dev/null @@ -1,12 +0,0 @@ - -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 index f4b2fbc..832f869 100644 --- a/tests/unit/data-structures/linked-list/index.ts +++ b/tests/unit/data-structures/linked-list/index.ts @@ -1,2 +1,2 @@ export * from './singly-linked-list.test'; -export * from './doubly-linked-list'; +export * from './doubly-linked-list.test'; 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 index bb8cc0f..90ea116 100644 --- a/tests/unit/data-structures/linked-list/singly-linked-list.test.ts +++ b/tests/unit/data-structures/linked-list/singly-linked-list.test.ts @@ -2,9 +2,10 @@ import {SinglyLinkedList} from '../../../../src'; describe('SinglyLinkedList Operation Test', () => { let list: SinglyLinkedList; - + let objectList: SinglyLinkedList<{ keyA: number }>; beforeEach(() => { list = new SinglyLinkedList(); + objectList = new SinglyLinkedList<{ keyA: number }>(); }); describe('push', () => { @@ -26,7 +27,7 @@ describe('SinglyLinkedList Operation Test', () => { it('should return undefined if the list is empty', () => { const popped = list.pop(); - expect(popped).toBeUndefined(); + expect(popped).toBeNull(); }); }); @@ -41,7 +42,7 @@ describe('SinglyLinkedList Operation Test', () => { it('should return undefined if the list is empty', () => { const shifted = list.shift(); - expect(shifted).toBeUndefined(); + expect(shifted).toBeNull(); }); }); @@ -65,7 +66,7 @@ describe('SinglyLinkedList Operation Test', () => { it('should return undefined for an out-of-bounds index', () => { list.push(1); const element = list.get(1); - expect(element).toBeUndefined(); + expect(element).toBeNull(); }); }); @@ -112,7 +113,7 @@ describe('SinglyLinkedList Operation Test', () => { list.push(1); list.push(2); list.push(3); - const removed = list.removeByValue(2); + const removed = list.delete(2); expect(removed).toBe(true); expect(list.toArray()).toEqual([1, 3]); }); @@ -121,7 +122,7 @@ describe('SinglyLinkedList Operation Test', () => { list.push(1); list.push(2); list.push(3); - const removed = list.removeByValue(4); + const removed = list.delete(4); expect(removed).toBe(false); expect(list.toArray()).toEqual([1, 2, 3]); }); @@ -245,21 +246,21 @@ describe('SinglyLinkedList Operation Test', () => { list.push(1); list.push(2); list.push(3); - const removed = list.remove(1); + const removed = list.deleteAt(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(); + const removed = list.deleteAt(1); + expect(removed).toBeNull(); }); it('should remove and return the first element', () => { list.push(1); list.push(2); - const removed = list.remove(0); + const removed = list.deleteAt(0); expect(removed).toBe(1); expect(list.toArray()).toEqual([2]); }); @@ -267,7 +268,7 @@ describe('SinglyLinkedList Operation Test', () => { it('should remove and return the last element', () => { list.push(1); list.push(2); - const removed = list.remove(1); + const removed = list.deleteAt(1); expect(removed).toBe(2); expect(list.toArray()).toEqual([1]); }); @@ -279,7 +280,7 @@ describe('SinglyLinkedList Operation Test', () => { list.push(2); expect(list.pop()).toBe(2); expect(list.pop()).toBe(1); - expect(list.pop()).toBeUndefined(); + expect(list.pop()).toBeNull(); }); }); @@ -289,7 +290,7 @@ describe('SinglyLinkedList Operation Test', () => { list.unshift(2); expect(list.shift()).toBe(2); expect(list.shift()).toBe(1); - expect(list.shift()).toBeUndefined(); + expect(list.shift()).toBeNull(); }); }); @@ -315,7 +316,7 @@ describe('SinglyLinkedList Operation Test', () => { list.push(1); list.push(3); const result = list.find((data) => data % 2 === 0); - expect(result).toBeUndefined(); + expect(result).toBeNull(); }); }); @@ -346,6 +347,35 @@ describe('SinglyLinkedList Operation Test', () => { expect(count).toBe(0); }); }); + + it('should insert and manipulate objects with numeric properties', () => { + const obj1 = {keyA: 1}; + const obj2 = {keyA: 2}; + const obj3 = {keyA: 3}; + + objectList.push(obj1); + objectList.push(obj2); + objectList.push(obj3); + + expect(objectList.toArray()).toEqual([obj1, obj2, obj3]); + + const newObj = {keyA: 2.5}; // Corrected newObj value + const insertSuccess = objectList.insertBefore(obj2, newObj); + expect(insertSuccess).toBe(true); + + const findNode = objectList.findNodeByValue(newObj); // Use newObj instead of obj2 + expect(findNode?.val).toEqual(newObj); + + const deleted = objectList.delete(newObj); // Use newObj instead of obj2 + expect(deleted).toBe(true); + + const poppedObj = objectList.pop(); + expect(poppedObj).toBe(obj3); + + const shiftedObj = objectList.shift(); + expect(shiftedObj).toBe(obj1); + }); + }); describe('SinglyLinkedList Performance Test', () => { diff --git a/tests/unit/data-structures/linked-list/skip-linked-list.ts b/tests/unit/data-structures/linked-list/skip-linked-list.ts index 3c4f75d..6ba4fd6 100644 --- a/tests/unit/data-structures/linked-list/skip-linked-list.ts +++ b/tests/unit/data-structures/linked-list/skip-linked-list.ts @@ -1,4 +1,3 @@ - describe('SkipLinkedList Operation Test', () => { it('should xxx', function () {